From aafeac2c2991ad419a0a46bfedc3c700e2e757a0 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Sun, 2 Jun 2024 04:13:20 +0300 Subject: [PATCH 01/11] Add Algorithm comparison and visualization dashboard --- .github/workflows/analysis.yml | 2 +- .github/workflows/documentation.yml | 118 ++++++------ .github/workflows/website.yml | 62 +++++++ website/index.css | 105 +++++++++++ website/index.html | 87 +++++++++ website/index.js | 276 ++++++++++++++++++++++++++++ 6 files changed, 590 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/website.yml create mode 100644 website/index.css create mode 100644 website/index.html create mode 100644 website/index.js diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 55f59cf..e3faa52 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -170,4 +170,4 @@ jobs: name: Comparison path: | test_reference.csv - test_results.csv + test_results.csv \ No newline at end of file diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 05ddf53..4504278 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,66 +1,66 @@ -name: Build & Deploy Documentation +# name: Build & Deploy Documentation -on: - workflow_run: - workflows: [Algorithm Analysis] - types: - - completed -permissions: - contents: read - pages: write - id-token: write -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v4 - - name: Set up Python - id: setup_python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: Cache pip - uses: actions/cache@v3 - id: pip-cache - with: - key: ${{ runner.os }}-${{ env.pythonLocation }}-pip-${{ hashFiles('**/requirements.txt') }} - path: ${{ env.pythonLocation }} - if: steps.pip-cache.outputs.cache-hit != 'true' +# on: +# workflow_run: +# workflows: [Algorithm Analysis] +# types: +# - completed +# permissions: +# contents: read +# pages: write +# id-token: write +# jobs: +# build: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - name: Setup Pages +# uses: actions/configure-pages@v4 +# - name: Set up Python +# id: setup_python +# uses: actions/setup-python@v5 +# with: +# python-version: "3.11" +# - name: Cache pip +# uses: actions/cache@v3 +# id: pip-cache +# with: +# key: ${{ runner.os }}-${{ env.pythonLocation }}-pip-${{ hashFiles('**/requirements.txt') }} +# path: ${{ env.pythonLocation }} +# if: steps.pip-cache.outputs.cache-hit != 'true' - - name: Install dependencies - run: | - pip install -r requirements.txt +# - name: Install dependencies +# run: | +# pip install -r requirements.txt - # Action to download artifacts from a different workflow (analysis.yml) - - name: 'Download artifact' - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ./.github/actions/download-artifact - with: - name: 'Figures' +# # Action to download artifacts from a different workflow (analysis.yml) +# - name: 'Download artifact' +# if: ${{ github.event.workflow_run.conclusion == 'success' }} +# uses: ./.github/actions/download-artifact +# with: +# name: 'Figures' - - name: Build html - run: | - mkdir docs/_static - mv *.pdf docs/_static/ - sphinx-apidoc -o docs src - cd docs/ - make html +# - name: Build html +# run: | +# mkdir docs/_static +# mv *.pdf docs/_static/ +# sphinx-apidoc -o docs src +# cd docs/ +# make html - - name: Upload docs artifact - uses: actions/upload-pages-artifact@v3 - with: - path: 'docs/_build/html' +# - name: Upload docs artifact +# uses: actions/upload-pages-artifact@v3 +# with: +# path: 'docs/_build/html' - deploy: - needs: build - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} +# deploy: +# needs: build +# environment: +# name: github-pages +# url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 +# runs-on: ubuntu-latest +# steps: +# - name: Deploy to GitHub Pages +# id: deployment +# uses: actions/deploy-pages@v4 diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 0000000..03bcea4 --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,62 @@ +name: Deploy dashboard website + +on: + workflow_run: + workflows: [Algorithm Analysis] + types: + - completed +permissions: + contents: read + pages: write + id-token: write +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Set up Python + id: setup_python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Cache pip + uses: actions/cache@v3 + id: pip-cache + with: + key: ${{ runner.os }}-${{ env.pythonLocation }}-pip-${{ hashFiles('**/requirements.txt') }} + path: ${{ env.pythonLocation }} + if: steps.pip-cache.outputs.cache-hit != 'true' + + - name: Install dependencies + run: | + pip install -r requirements.txt + + # Action to download artifacts from a different workflow (analysis.yml) + - name: 'Download artifact' + if: ${{ github.event.workflow_run.conclusion == 'success' }} + uses: ./.github/actions/download-artifact + with: + name: 'Data' + + - name: move data to the website folder + run: | + mv test_output.csv website/ + + - name: Upload website artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'website' + + deploy: + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/website/index.css b/website/index.css new file mode 100644 index 0000000..be2d2fb --- /dev/null +++ b/website/index.css @@ -0,0 +1,105 @@ +:root { + --primary-color: #072A6A; + --accent-color: #62D58A; + --primary-white: #fff; + --background-color: #f4f4f9; + --shadow: rgba(0, 0, 0, 0.2); +} + +body { + margin: 0; + padding: 0; + font-family: 'Inter', 'Atkinson Hyperlegible', sans-serif; + background-color: var(--background-color); +} + +header { + padding: 1rem 2rem; + background-color: var(--primary-color); + color: var(--primary-white); +} + +.bar-title { + margin: 0; + font-size: 2rem; + font-weight: 600; +} + +.divider { + margin: 0.5rem 0; + border: none; + border-top: 2px solid var(--accent-color); +} + +main { + padding: 2rem; +} + +.filter-wrapper { + margin: 2rem; + font-size: x-large; + font-weight: bold; +} + +.filter-menu { + padding: 0.5rem 1rem; + border: none; + border-radius: 5px; + background-color: var(--primary-color); + color: var(--primary-white); + font-size: 1rem; + cursor: pointer; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.filter-menu:hover { + background-color: var(--accent-color); +} + +.chart-card { + padding: 2rem; + border-radius: 1rem; + box-shadow: 0px 8px 16px var(--shadow); + background-color: var(--primary-white); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.chart-card:hover { + transform: translateY(-5px); + box-shadow: 0px 12px 24px var(--shadow); +} + +.slider-container { + display: flex; + align-items: center; + margin: 20px 0; + width: 30%; +} +.slider-label { + margin-right: 10px; + font-weight: bold; + color: #333; +} +.slider { + margin: 0 10px; + flex-grow: 1; + height: 16px; + background: #ddd; + outline: none; + opacity: 0.9; + transition: opacity 1.2s; +} +.slider:hover { + opacity: 1; + width: 25px; + height: 25px; + cursor: pointer; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); +} + +.slider-value { + margin-left: 10px; + font-weight: bold; + color: #333; +} + diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..f55d915 --- /dev/null +++ b/website/index.html @@ -0,0 +1,87 @@ + + + + + + IVIM MRI Algorithm Fitting Dashboard + + + + + + + + +
+
+

IVIM MRI Algorithm Fitting Dashboard

+
+
+
+
+ + + + + + + + + +
+ Range: + + + + 2x +
+
+
+ +
+ + + +
+ + + + + + + + + +
+ Range: + + + + 2x +
+
+
+ +
+ + +
+
+ + diff --git a/website/index.js b/website/index.js new file mode 100644 index 0000000..e76bc27 --- /dev/null +++ b/website/index.js @@ -0,0 +1,276 @@ +document.addEventListener('DOMContentLoaded', function() { + let data; + let selectedAlgorithm = 'ETP_SRI_LinearFitting'; + let selectedSNR = '10'; + let selectedType = 'D_fitted'; + let selectedRange = 2; + let selectedRegion = 'Liver'; + let selectedSNRRegion = '10'; + let selectedTypeRegion = 'D_fitted'; + let selectedRangeRegion = 2; + // Add event listener to algorithm select + const algorithmSelect = document.getElementById('algorithm-select'); + algorithmSelect.addEventListener('change', function(event) { + selectedAlgorithm = event.target.value; + drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + }); + + // Add event listener to SNR select + const snrSelect = document.getElementById('snr-select'); + snrSelect.addEventListener('change', function(event) { + selectedSNR = event.target.value; + drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + }); + + + + // Add event listener to type select + const typeSelect = document.getElementById('type-select'); + typeSelect.addEventListener('change', function(event) { + selectedType = event.target.value; + drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + }); + + // Add event listeners to range slider and buttons + const rangeSlider = document.getElementById('range-slider'); + const rangeValue = document.getElementById('range-value'); + const decrementRange = document.getElementById('decrement-range'); + const incrementRange = document.getElementById('increment-range'); + + function updateRange(value) { + selectedRange = value; + rangeValue.textContent = value; + drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + } + + rangeSlider.addEventListener('input', function(event) { + updateRange(event.target.value); + }); + + decrementRange.addEventListener('click', function() { + let newValue = parseInt(rangeSlider.value) - 2; + if (newValue >= 2) { + rangeSlider.value = newValue; + updateRange(newValue); + } + }); + + incrementRange.addEventListener('click', function() { + let newValue = parseInt(rangeSlider.value) + 2; + if (newValue <= 100) { + rangeSlider.value = newValue; + updateRange(newValue); + } + }); + + // Add event listener to region select + const regionSelect = document.getElementById('region-select'); + regionSelect.addEventListener('change', function(event) { + selectedRegion = event.target.value; + drawRegionBoxPlot(data, selectedRegion, selectedSNR, selectedType, selectedRange); + }); + // Add event listener to SNR select + const snrRegionSelect = document.getElementById('snr-region-select'); + snrRegionSelect.addEventListener('change', function(event) { + selectedSNRRegion = event.target.value; + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + }); + + // Add event listener to type select + const typeRegionSelect = document.getElementById('type-region-select'); + typeRegionSelect.addEventListener('change', function(event) { + selectedTypeRegion = event.target.value; + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + }); + + // Add event listeners to range region slider and buttons + const rangeSliderRegion = document.getElementById('range-slider-region'); + const rangeValueRegion = document.getElementById('range-value-region'); + const decrementRangeRegion = document.getElementById('decrement-range-region'); + const incrementRangeRegion = document.getElementById('increment-range-region'); + + function updateRangeRegion(value) { + selectedRangeRegion = value; + rangeValueRegion.textContent = value; + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + } + + rangeSliderRegion.addEventListener('input', function(event) { + updateRangeRegion(event.target.value); + }); + + decrementRangeRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderRegion.value) - 2; + if (newValue >= 2) { + rangeSliderRegion.value = newValue; + updateRangeRegion(newValue); + } + }); + + incrementRangeRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderRegion.value) + 2; + if (newValue <= 100) { + rangeSliderRegion.value = newValue; + updateRangeRegion(newValue); + } + }); + + Papa.parse('test_output.csv', { + download: true, + header: true, + complete: results => { + data = results; + populateOptions(data); + drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + + } + }); + + function populateOptions(data) { + + //-----Algorithms options------ + const AlgorithmsSet = new Set(data.data.map(obj => obj.Algorithm)); + const algorithms = Array.from(AlgorithmsSet); + const algorithmSelect = document.getElementById('algorithm-select'); + algorithms.forEach(algorithm => { + let option = document.createElement('option'); + option.value = algorithm; + option.textContent = algorithm; + algorithmSelect.appendChild(option); + }); + + //-----Regions options------ + const regionsSet = new Set(data.data.map(obj => obj.Region)); + const regions = Array.from(regionsSet); + const regionSelect = document.getElementById('region-select'); + regions.forEach(region => { + let option = document.createElement('option'); + option.value = region; + option.textContent = region; + regionSelect.appendChild(option); + }); + + //-----SNR options for algorithms and regions------ + const snrSet = new Set(data.data.map(obj => obj.SNR)); + const snr = Array.from(snrSet); + console.log(snr) + const snrSelect = document.getElementById('snr-select'); + const snrRegionSelect = document.getElementById('snr-region-select'); + snr.forEach(snrValue => { + let option = document.createElement('option'); + option.value = snrValue; + option.textContent = snrValue; + snrSelect.appendChild(option); + }); + snr.forEach(snrValue => { + let option = document.createElement('option'); + option.value = snrValue; + option.textContent = snrValue; + snrRegionSelect.appendChild(option); + }); + + } + + function drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange) { + let jsonData = data.data.filter(obj => obj.Algorithm === selectedAlgorithm); + const type = { + "D_fitted": "Diffusion", + "Dp_fitted": "Perfusion", + "f_fitted": "Perfusion Fraction" + }; + let plots = []; + const regions = new Set(jsonData.map(obj => obj.Region)); + const uniqueRegionsArray = Array.from(regions); + uniqueRegionsArray.forEach(region => { + const D_fittedValues = jsonData + .filter(obj => obj.Region === region) + .filter(obj => obj.SNR === selectedSNR) + .filter(obj => Math.abs(obj[selectedType]) < (Math.abs(obj[selectedType.slice(0, -7)]) * selectedRange)) + .map(obj => obj[selectedType]); + var plot = { + y: D_fittedValues, + type: 'box', + name: region, + marker: { + outliercolor: 'white)', + }, + boxpoints: 'Outliers' + }; + plots.push(plot); + const groundTruth = jsonData + .filter(obj => obj.Region === region) + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType.slice(0, -7)]); + var constantPoint = { + x: [region], + y: groundTruth, + type: 'scatter', + mode: 'markers', + marker: { + color: 'black', + size: 10 + }, + showlegend: false + }; + plots.push(constantPoint); + }); + + var layout = { + title: `${type[selectedType]} Box Plots for ${selectedAlgorithm} algorithm with ${selectedSNR} SNR` + }; + + Plotly.newPlot('myDiv', plots, layout); + } + + function drawRegionBoxPlot(data, selectedRegion, selectedSNR, selectedType, selectedRange) { + let jsonData = data.data.filter(obj => obj.Region === selectedRegion); + const type = { + "D_fitted": "Diffusion", + "Dp_fitted": "Perfusion", + "f_fitted": "Perfusion Fraction" + }; + let plots = []; + const algorithms = new Set(jsonData.map(obj => obj.Algorithm)); + const uniqueAlgorithmsArray = Array.from(algorithms); + uniqueAlgorithmsArray.forEach(algorithm => { + const D_fittedValues = jsonData + .filter(obj => obj.Algorithm === algorithm) + .filter(obj => obj.SNR === selectedSNR) + .filter(obj => Math.abs(obj[selectedType]) < (Math.abs(obj[selectedType.slice(0, -7)]) * selectedRange)) + .map(obj => obj[selectedType]); + var plot = { + y: D_fittedValues, + type: 'box', + name: algorithm, + marker: { + outliercolor: 'white)', + }, + boxpoints: 'Outliers' + }; + plots.push(plot); + const groundTruth = jsonData + .filter(obj => obj.Algorithm === algorithm) + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType.slice(0, -7)]); + var constantPoint = { + x: [algorithm], + y: groundTruth, + type: 'scatter', + mode: 'markers', + marker: { + color: 'black', + size: 10 + }, + showlegend: false + }; + plots.push(constantPoint); + }); + + var layout = { + title: `${type[selectedType]} Box Plots for ${selectedRegion} region with ${selectedSNR} SNR` + }; + + Plotly.newPlot('regionDiv', plots, layout); + } +}); From a0ac4424340e6faab0f9311f062c14d75e7a1a57 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Mon, 3 Jun 2024 20:25:31 +0300 Subject: [PATCH 02/11] Add documentation to a separate route on the website. --- .github/workflows/analysis.yml | 2 +- .github/workflows/documentation.yml | 66 ----------------------------- .github/workflows/website.yml | 24 +++++++++-- 3 files changed, 22 insertions(+), 70 deletions(-) delete mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index e3faa52..55f59cf 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -170,4 +170,4 @@ jobs: name: Comparison path: | test_reference.csv - test_results.csv \ No newline at end of file + test_results.csv diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml deleted file mode 100644 index 4504278..0000000 --- a/.github/workflows/documentation.yml +++ /dev/null @@ -1,66 +0,0 @@ -# name: Build & Deploy Documentation - -# on: -# workflow_run: -# workflows: [Algorithm Analysis] -# types: -# - completed -# permissions: -# contents: read -# pages: write -# id-token: write -# jobs: -# build: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v4 -# - name: Setup Pages -# uses: actions/configure-pages@v4 -# - name: Set up Python -# id: setup_python -# uses: actions/setup-python@v5 -# with: -# python-version: "3.11" -# - name: Cache pip -# uses: actions/cache@v3 -# id: pip-cache -# with: -# key: ${{ runner.os }}-${{ env.pythonLocation }}-pip-${{ hashFiles('**/requirements.txt') }} -# path: ${{ env.pythonLocation }} -# if: steps.pip-cache.outputs.cache-hit != 'true' - -# - name: Install dependencies -# run: | -# pip install -r requirements.txt - -# # Action to download artifacts from a different workflow (analysis.yml) -# - name: 'Download artifact' -# if: ${{ github.event.workflow_run.conclusion == 'success' }} -# uses: ./.github/actions/download-artifact -# with: -# name: 'Figures' - -# - name: Build html -# run: | -# mkdir docs/_static -# mv *.pdf docs/_static/ -# sphinx-apidoc -o docs src -# cd docs/ -# make html - -# - name: Upload docs artifact -# uses: actions/upload-pages-artifact@v3 -# with: -# path: 'docs/_build/html' - -# deploy: -# needs: build -# environment: -# name: github-pages -# url: ${{ steps.deployment.outputs.page_url }} - -# runs-on: ubuntu-latest -# steps: -# - name: Deploy to GitHub Pages -# id: deployment -# uses: actions/deploy-pages@v4 diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 03bcea4..758c676 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -1,4 +1,4 @@ -name: Deploy dashboard website +name: Build & Deploy Website on: workflow_run: @@ -33,8 +33,14 @@ jobs: run: | pip install -r requirements.txt - # Action to download artifacts from a different workflow (analysis.yml) + # Action Figures artifact - name: 'Download artifact' + if: ${{ github.event.workflow_run.conclusion == 'success' }} + uses: ./.github/actions/download-artifact + with: + name: 'Figures' + # Action analysis data artifact + - name: 'Download analysis data' if: ${{ github.event.workflow_run.conclusion == 'success' }} uses: ./.github/actions/download-artifact with: @@ -44,7 +50,19 @@ jobs: run: | mv test_output.csv website/ - - name: Upload website artifact + - name: Build documentation + run: | + mkdir docs/_static + mv *.pdf docs/_static/ + sphinx-apidoc -o docs src + cd docs/ + make html + + - name: move data to the website folder + run: | + mv docs/_build/html website/documentation + + - name: Upload docs artifact uses: actions/upload-pages-artifact@v3 with: path: 'website' From ac6e6c65c70f69ab3e11beed96cf76df4ba74fce Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Wed, 5 Jun 2024 17:30:18 +0300 Subject: [PATCH 03/11] Add loading spinner. --- website/index.css | 27 +++++++++++++++++++++++++++ website/index.html | 3 +++ website/index.js | 15 +++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/website/index.css b/website/index.css index be2d2fb..db68a04 100644 --- a/website/index.css +++ b/website/index.css @@ -103,3 +103,30 @@ main { color: #333; } +.spinner { + border: 8px solid rgba(0, 0, 0, 0.1); + width: 76px; + height: 76px; + border-radius: 50%; + border-top-color: #3498db; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.hidden { + display: none; +} diff --git a/website/index.html b/website/index.html index f55d915..bcea719 100644 --- a/website/index.html +++ b/website/index.html @@ -13,6 +13,9 @@
+

IVIM MRI Algorithm Fitting Dashboard


diff --git a/website/index.js b/website/index.js index e76bc27..1c65242 100644 --- a/website/index.js +++ b/website/index.js @@ -8,6 +8,19 @@ document.addEventListener('DOMContentLoaded', function() { let selectedSNRRegion = '10'; let selectedTypeRegion = 'D_fitted'; let selectedRangeRegion = 2; + + const loadingOverlay = document.getElementById('loadingOverlay'); + const mainContent = document.getElementsByTagName('main')[0]; + function showLoading() { + mainContent.classList.add('hidden'); + loadingOverlay.classList.remove('hidden'); + } + + function hideLoading() { + loadingOverlay.classList.add('hidden'); + mainContent.classList.remove('hidden') + } + // Add event listener to algorithm select const algorithmSelect = document.getElementById('algorithm-select'); algorithmSelect.addEventListener('change', function(event) { @@ -114,12 +127,14 @@ document.addEventListener('DOMContentLoaded', function() { updateRangeRegion(newValue); } }); + showLoading(); Papa.parse('test_output.csv', { download: true, header: true, complete: results => { data = results; + hideLoading(); populateOptions(data); drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); From 0a937d65dfe557e0b17f09fd2e3bba7dfdf5846f Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Tue, 18 Jun 2024 17:25:02 +0300 Subject: [PATCH 04/11] Limit the range to the max useful value & Add ground truth legend. --- website/index.js | 118 +++++++++++++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/website/index.js b/website/index.js index 1c65242..86e25a9 100644 --- a/website/index.js +++ b/website/index.js @@ -25,6 +25,7 @@ document.addEventListener('DOMContentLoaded', function() { const algorithmSelect = document.getElementById('algorithm-select'); algorithmSelect.addEventListener('change', function(event) { selectedAlgorithm = event.target.value; + updateRange('2') drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); }); @@ -32,15 +33,15 @@ document.addEventListener('DOMContentLoaded', function() { const snrSelect = document.getElementById('snr-select'); snrSelect.addEventListener('change', function(event) { selectedSNR = event.target.value; + updateRange('2') drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); }); - - // Add event listener to type select const typeSelect = document.getElementById('type-select'); typeSelect.addEventListener('change', function(event) { selectedType = event.target.value; + updateRange('2') drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); }); @@ -49,10 +50,10 @@ document.addEventListener('DOMContentLoaded', function() { const rangeValue = document.getElementById('range-value'); const decrementRange = document.getElementById('decrement-range'); const incrementRange = document.getElementById('increment-range'); - function updateRange(value) { selectedRange = value; rangeValue.textContent = value; + rangeSlider.value = value drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); } @@ -70,7 +71,7 @@ document.addEventListener('DOMContentLoaded', function() { incrementRange.addEventListener('click', function() { let newValue = parseInt(rangeSlider.value) + 2; - if (newValue <= 100) { + if (newValue <= rangeSlider.max) { rangeSlider.value = newValue; updateRange(newValue); } @@ -80,53 +81,59 @@ document.addEventListener('DOMContentLoaded', function() { const regionSelect = document.getElementById('region-select'); regionSelect.addEventListener('change', function(event) { selectedRegion = event.target.value; - drawRegionBoxPlot(data, selectedRegion, selectedSNR, selectedType, selectedRange); + updateRangeRegion('2') + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); }); - // Add event listener to SNR select - const snrRegionSelect = document.getElementById('snr-region-select'); - snrRegionSelect.addEventListener('change', function(event) { - selectedSNRRegion = event.target.value; - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); - }); - - // Add event listener to type select - const typeRegionSelect = document.getElementById('type-region-select'); - typeRegionSelect.addEventListener('change', function(event) { - selectedTypeRegion = event.target.value; - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); - }); - - // Add event listeners to range region slider and buttons - const rangeSliderRegion = document.getElementById('range-slider-region'); - const rangeValueRegion = document.getElementById('range-value-region'); - const decrementRangeRegion = document.getElementById('decrement-range-region'); - const incrementRangeRegion = document.getElementById('increment-range-region'); - - function updateRangeRegion(value) { - selectedRangeRegion = value; - rangeValueRegion.textContent = value; - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); - } - rangeSliderRegion.addEventListener('input', function(event) { - updateRangeRegion(event.target.value); - }); + // Add event listener to SNR select + const snrRegionSelect = document.getElementById('snr-region-select'); + snrRegionSelect.addEventListener('change', function(event) { + selectedSNRRegion = event.target.value; + updateRangeRegion('2') + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + }); - decrementRangeRegion.addEventListener('click', function() { - let newValue = parseInt(rangeSliderRegion.value) - 2; - if (newValue >= 2) { - rangeSliderRegion.value = newValue; - updateRangeRegion(newValue); - } - }); + // Add event listener to type select + const typeRegionSelect = document.getElementById('type-region-select'); + typeRegionSelect.addEventListener('change', function(event) { + selectedTypeRegion = event.target.value; + updateRangeRegion('2') + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + }); + + // Add event listeners to range region slider and buttons + const rangeSliderRegion = document.getElementById('range-slider-region'); + const rangeValueRegion = document.getElementById('range-value-region'); + const decrementRangeRegion = document.getElementById('decrement-range-region'); + const incrementRangeRegion = document.getElementById('increment-range-region'); + + function updateRangeRegion(value) { + selectedRangeRegion = value; + rangeValueRegion.textContent = value; + rangeSliderRegion.value = value + drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + } + + rangeSliderRegion.addEventListener('input', function(event) { + updateRangeRegion(event.target.value); + }); + + decrementRangeRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderRegion.value) - 2; + if (newValue >= 2) { + rangeSliderRegion.value = newValue; + updateRangeRegion(newValue); + } + }); incrementRangeRegion.addEventListener('click', function() { let newValue = parseInt(rangeSliderRegion.value) + 2; - if (newValue <= 100) { + if (newValue <= rangeSliderRegion.max) { rangeSliderRegion.value = newValue; updateRangeRegion(newValue); } }); + showLoading(); Papa.parse('test_output.csv', { @@ -194,6 +201,17 @@ document.addEventListener('DOMContentLoaded', function() { "Dp_fitted": "Perfusion", "f_fitted": "Perfusion Fraction" }; + + const allD_fittedValues = jsonData + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType]); + + const maxValue = Math.max(...allD_fittedValues.map(Math.abs)); + const groundTruthList = jsonData + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType.slice(0, -7)]); + let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) + rangeSlider.max = Math.round(estimatedRange / 2) * 2 let plots = []; const regions = new Set(jsonData.map(obj => obj.Region)); const uniqueRegionsArray = Array.from(regions); @@ -208,7 +226,7 @@ document.addEventListener('DOMContentLoaded', function() { type: 'box', name: region, marker: { - outliercolor: 'white)', + outliercolor: 'white', }, boxpoints: 'Outliers' }; @@ -221,12 +239,12 @@ document.addEventListener('DOMContentLoaded', function() { x: [region], y: groundTruth, type: 'scatter', + name: region + ' ground truth', mode: 'markers', marker: { color: 'black', size: 10 }, - showlegend: false }; plots.push(constantPoint); }); @@ -245,6 +263,18 @@ document.addEventListener('DOMContentLoaded', function() { "Dp_fitted": "Perfusion", "f_fitted": "Perfusion Fraction" }; + + const allD_fittedValues = jsonData + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType]); + + const maxValue = Math.max(...allD_fittedValues.map(Math.abs)); + const groundTruthList = jsonData + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType.slice(0, -7)]); + let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) + rangeSliderRegion.max = Math.round(estimatedRange / 2) * 2 + let plots = []; const algorithms = new Set(jsonData.map(obj => obj.Algorithm)); const uniqueAlgorithmsArray = Array.from(algorithms); @@ -272,12 +302,12 @@ document.addEventListener('DOMContentLoaded', function() { x: [algorithm], y: groundTruth, type: 'scatter', + name: algorithm + ' ground truth', mode: 'markers', marker: { color: 'black', size: 10 }, - showlegend: false }; plots.push(constantPoint); }); From c0c6237e5320d2d91f7d38aabb255fa891cbee59 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Tue, 18 Jun 2024 17:50:25 +0300 Subject: [PATCH 05/11] Separate the build folder. --- {website => dashboard_website}/index.css | 0 {website => dashboard_website}/index.html | 0 {website => dashboard_website}/index.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {website => dashboard_website}/index.css (100%) rename {website => dashboard_website}/index.html (100%) rename {website => dashboard_website}/index.js (100%) diff --git a/website/index.css b/dashboard_website/index.css similarity index 100% rename from website/index.css rename to dashboard_website/index.css diff --git a/website/index.html b/dashboard_website/index.html similarity index 100% rename from website/index.html rename to dashboard_website/index.html diff --git a/website/index.js b/dashboard_website/index.js similarity index 100% rename from website/index.js rename to dashboard_website/index.js From 745829b9f908f5fd8d03a5fc76835eac4ec4b105 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Tue, 18 Jun 2024 17:51:21 +0300 Subject: [PATCH 06/11] add the new build path --- .github/workflows/website.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 758c676..8b009a3 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -48,7 +48,7 @@ jobs: - name: move data to the website folder run: | - mv test_output.csv website/ + mv test_output.csv dashboard_website/ - name: Build documentation run: | @@ -60,12 +60,12 @@ jobs: - name: move data to the website folder run: | - mv docs/_build/html website/documentation + mv "docs/_build/html" "dashboard_website/documentation" - name: Upload docs artifact uses: actions/upload-pages-artifact@v3 with: - path: 'website' + path: 'dashboard_website' deploy: needs: build From 0e14d2305386bb8dd24f4482f6794ed1f71d8ea4 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Wed, 3 Jul 2024 17:54:30 +0300 Subject: [PATCH 07/11] Add upper and lower range sliders. --- dashboard_website/index.html | 27 ++++- dashboard_website/index.js | 200 ++++++++++++++++++++++++++--------- 2 files changed, 171 insertions(+), 56 deletions(-) diff --git a/dashboard_website/index.html b/dashboard_website/index.html index bcea719..61765a6 100644 --- a/dashboard_website/index.html +++ b/dashboard_website/index.html @@ -40,12 +40,21 @@

IVIM MRI Algorithm Fitting Dashboard

- Range: + Upper Range: + 0 - + - 2x + 2
+
+ Lower Range: + 0 + + + + 2 +
@@ -72,12 +81,20 @@

IVIM MRI Algorithm Fitting Dashboard

- Range: + Upper Range: - 2x + 2
+
+ Lower Range: + 0 + + + + 2 +
diff --git a/dashboard_website/index.js b/dashboard_website/index.js index 86e25a9..9810dc7 100644 --- a/dashboard_website/index.js +++ b/dashboard_website/index.js @@ -3,11 +3,18 @@ document.addEventListener('DOMContentLoaded', function() { let selectedAlgorithm = 'ETP_SRI_LinearFitting'; let selectedSNR = '10'; let selectedType = 'D_fitted'; - let selectedRange = 2; + let selectedRange = 10; + let selectedRangeLower = 2; let selectedRegion = 'Liver'; let selectedSNRRegion = '10'; let selectedTypeRegion = 'D_fitted'; - let selectedRangeRegion = 2; + let selectedRangeRegion = 10; + let selectedRangeLowerRegion = 2; + const type = { + "D_fitted": "Diffusion", + "Dp_fitted": "Perfusion", + "f_fitted": "Perfusion Fraction" + }; const loadingOverlay = document.getElementById('loadingOverlay'); const mainContent = document.getElementsByTagName('main')[0]; @@ -25,24 +32,27 @@ document.addEventListener('DOMContentLoaded', function() { const algorithmSelect = document.getElementById('algorithm-select'); algorithmSelect.addEventListener('change', function(event) { selectedAlgorithm = event.target.value; - updateRange('2') - drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + updateRange('10') + updateRangeLower('2') + drawBoxPlot(); }); // Add event listener to SNR select const snrSelect = document.getElementById('snr-select'); snrSelect.addEventListener('change', function(event) { selectedSNR = event.target.value; - updateRange('2') - drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + updateRange('10') + updateRangeLower('2') + drawBoxPlot(); }); // Add event listener to type select const typeSelect = document.getElementById('type-select'); typeSelect.addEventListener('change', function(event) { selectedType = event.target.value; - updateRange('2') - drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + updateRange('10') + updateRangeLower('2') + drawBoxPlot(); }); // Add event listeners to range slider and buttons @@ -52,9 +62,9 @@ document.addEventListener('DOMContentLoaded', function() { const incrementRange = document.getElementById('increment-range'); function updateRange(value) { selectedRange = value; - rangeValue.textContent = value; + //rangeValue.textContent = value; rangeSlider.value = value - drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); + drawBoxPlot(); } rangeSlider.addEventListener('input', function(event) { @@ -77,28 +87,63 @@ document.addEventListener('DOMContentLoaded', function() { } }); + // Add event listeners to range slider and buttons + const rangeSliderLower = document.getElementById('lower-range-slider'); + const rangeValueLower = document.getElementById('lower-range-value'); + const decrementRangeLower = document.getElementById('decrement-lower-range'); + const incrementRangeLower = document.getElementById('increment-lower-range'); + function updateRangeLower(value) { + selectedRangeLower = value; + //rangeValue.textContent = value; + rangeSliderLower.value = value + drawBoxPlot(); + } + + rangeSliderLower.addEventListener('input', function(event) { + updateRangeLower(event.target.value); + }); + + decrementRangeLower.addEventListener('click', function() { + let newValue = parseInt(rangeSlider.value) - 2; + if (newValue >= 2) { + rangeSliderLower.value = newValue; + updateRangeLower(newValue); + } + }); + + incrementRangeLower.addEventListener('click', function() { + let newValue = parseInt(rangeSliderLower.value) + 2; + if (newValue <= rangeSliderLower.max) { + rangeSliderLower.value = newValue; + updateRangeLower(newValue); + } + }); + // Add event listener to region select const regionSelect = document.getElementById('region-select'); regionSelect.addEventListener('change', function(event) { selectedRegion = event.target.value; - updateRangeRegion('2') - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + updateRangeRegion('10') + updateRangeLowerRegion('2') + drawRegionBoxPlot(); }); // Add event listener to SNR select const snrRegionSelect = document.getElementById('snr-region-select'); snrRegionSelect.addEventListener('change', function(event) { selectedSNRRegion = event.target.value; - updateRangeRegion('2') - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + updateRangeRegion('10') + updateRangeLowerRegion('2') + drawRegionBoxPlot(); }); // Add event listener to type select const typeRegionSelect = document.getElementById('type-region-select'); typeRegionSelect.addEventListener('change', function(event) { selectedTypeRegion = event.target.value; - updateRangeRegion('2') - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + updateRangeRegion('10') + updateRangeLowerRegion('2') + drawRegionBoxPlot(); }); // Add event listeners to range region slider and buttons @@ -109,9 +154,9 @@ document.addEventListener('DOMContentLoaded', function() { function updateRangeRegion(value) { selectedRangeRegion = value; - rangeValueRegion.textContent = value; + //rangeValueRegion.textContent = value; rangeSliderRegion.value = value - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + drawRegionBoxPlot(); } rangeSliderRegion.addEventListener('input', function(event) { @@ -134,6 +179,38 @@ document.addEventListener('DOMContentLoaded', function() { } }); + // Add event listeners to range slider and buttons for region + const rangeSliderLowerRegion = document.getElementById('lower-range-slider-region'); + const rangeValueLowerRegion = document.getElementById('lower-range-value-region'); + const decrementRangeLowerRegion = document.getElementById('decrement-lower-range-region'); + const incrementRangeLowerRegion = document.getElementById('increment-lower-range-region'); + function updateRangeLowerRegion(value) { + selectedRangeLowerRegion = value; + //rangeValue.textContent = value; + rangeSliderLowerRegion.value = value + drawRegionBoxPlot(); + } + + rangeSliderLowerRegion.addEventListener('input', function(event) { + updateRangeLowerRegion(event.target.value); + }); + + decrementRangeLowerRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderRegion.value) - 2; + if (newValue >= 2) { + rangeSliderLowerRegion.value = newValue; + updateRangeLowerRegion(newValue); + } + }); + + incrementRangeLowerRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderLowerRegion.value) + 2; + if (newValue <= rangeSliderLowerRegion.max) { + rangeSliderLowerRegion.value = newValue; + updateRangeLowerRegion(newValue); + } + }); + showLoading(); Papa.parse('test_output.csv', { @@ -143,8 +220,8 @@ document.addEventListener('DOMContentLoaded', function() { data = results; hideLoading(); populateOptions(data); - drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange); - drawRegionBoxPlot(data, selectedRegion, selectedSNRRegion, selectedTypeRegion, selectedRangeRegion); + drawBoxPlot(); + drawRegionBoxPlot(); } }); @@ -194,24 +271,30 @@ document.addEventListener('DOMContentLoaded', function() { } - function drawBoxPlot(data, selectedAlgorithm, selectedSNR, selectedType, selectedRange) { + function drawBoxPlot() { let jsonData = data.data.filter(obj => obj.Algorithm === selectedAlgorithm); - const type = { - "D_fitted": "Diffusion", - "Dp_fitted": "Perfusion", - "f_fitted": "Perfusion Fraction" - }; const allD_fittedValues = jsonData .filter(obj => obj.SNR === selectedSNR) .map(obj => obj[selectedType]); - const maxValue = Math.max(...allD_fittedValues.map(Math.abs)); + const maxValue = Math.max(...allD_fittedValues); + const minValue = Math.min(...allD_fittedValues); const groundTruthList = jsonData - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType.slice(0, -7)]); + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType.slice(0, -7)]); let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) - rangeSlider.max = Math.round(estimatedRange / 2) * 2 + let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) + if (minValue < 0) { + rangeValueLower.textContent = minValue.toFixed(4) + } + else { + rangeValueLower.textContent = '0.0000' + selectedRangeLower = 0; + } + rangeValue.textContent = maxValue.toFixed(4) + rangeSlider.max = Math.round(estimatedRange / 2) * 2 + rangeSliderLower.max = Math.round(estimatedRangeLower / 2) * 2 let plots = []; const regions = new Set(jsonData.map(obj => obj.Region)); const uniqueRegionsArray = Array.from(regions); @@ -219,7 +302,6 @@ document.addEventListener('DOMContentLoaded', function() { const D_fittedValues = jsonData .filter(obj => obj.Region === region) .filter(obj => obj.SNR === selectedSNR) - .filter(obj => Math.abs(obj[selectedType]) < (Math.abs(obj[selectedType.slice(0, -7)]) * selectedRange)) .map(obj => obj[selectedType]); var plot = { y: D_fittedValues, @@ -250,30 +332,42 @@ document.addEventListener('DOMContentLoaded', function() { }); var layout = { - title: `${type[selectedType]} Box Plots for ${selectedAlgorithm} algorithm with ${selectedSNR} SNR` + title: `${type[selectedType]} Box Plots for ${selectedAlgorithm} algorithm with ${selectedSNR} SNR`, + yaxis: { + autorange: false, + range: [-Math.min(...groundTruthList) * selectedRangeLower, Math.min(...groundTruthList) * selectedRange], + type: 'linear' + } }; Plotly.newPlot('myDiv', plots, layout); } - function drawRegionBoxPlot(data, selectedRegion, selectedSNR, selectedType, selectedRange) { + function drawRegionBoxPlot() { let jsonData = data.data.filter(obj => obj.Region === selectedRegion); - const type = { - "D_fitted": "Diffusion", - "Dp_fitted": "Perfusion", - "f_fitted": "Perfusion Fraction" - }; const allD_fittedValues = jsonData - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType]); + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion]); + + const maxValue = Math.max(...allD_fittedValues); + const minValue = Math.min(...allD_fittedValues); - const maxValue = Math.max(...allD_fittedValues.map(Math.abs)); const groundTruthList = jsonData - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType.slice(0, -7)]); + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion.slice(0, -7)]); let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) - rangeSliderRegion.max = Math.round(estimatedRange / 2) * 2 + let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) + if (minValue < 0) { + rangeValueLowerRegion.textContent = minValue.toFixed(4) + } + else { + rangeValueLowerRegion.textContent = '0.0000' + selectedRangeLowerRegion = 0; + } + rangeValueRegion.textContent = maxValue.toFixed(4) + rangeSliderRegion.max = Math.round(estimatedRange / 2) * 2 + rangeSliderLowerRegion.max = Math.round(estimatedRangeLower / 2) * 2 let plots = []; const algorithms = new Set(jsonData.map(obj => obj.Algorithm)); @@ -281,23 +375,22 @@ document.addEventListener('DOMContentLoaded', function() { uniqueAlgorithmsArray.forEach(algorithm => { const D_fittedValues = jsonData .filter(obj => obj.Algorithm === algorithm) - .filter(obj => obj.SNR === selectedSNR) - .filter(obj => Math.abs(obj[selectedType]) < (Math.abs(obj[selectedType.slice(0, -7)]) * selectedRange)) - .map(obj => obj[selectedType]); + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion]); var plot = { y: D_fittedValues, type: 'box', name: algorithm, marker: { - outliercolor: 'white)', + outliercolor: 'white', }, boxpoints: 'Outliers' }; plots.push(plot); const groundTruth = jsonData .filter(obj => obj.Algorithm === algorithm) - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType.slice(0, -7)]); + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion.slice(0, -7)]); var constantPoint = { x: [algorithm], y: groundTruth, @@ -313,7 +406,12 @@ document.addEventListener('DOMContentLoaded', function() { }); var layout = { - title: `${type[selectedType]} Box Plots for ${selectedRegion} region with ${selectedSNR} SNR` + title: `${type[selectedTypeRegion]} Box Plots for ${selectedRegion} region with ${selectedSNR} SNR`, + yaxis: { + autorange: false, + range: [-Math.min(...groundTruthList) * selectedRangeLowerRegion, Math.min(...groundTruthList) * selectedRangeRegion], + type: 'linear' + } }; Plotly.newPlot('regionDiv', plots, layout); From 875b1932288934e617388ec332e6e536c1765ecb Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Fri, 19 Jul 2024 09:45:51 +0300 Subject: [PATCH 08/11] Add website landing page. --- .github/workflows/website.yml | 8 +- {website => html_report}/combined_report.html | 0 website/dashboard/index.css | 132 ++++++ website/dashboard/index.html | 107 +++++ website/dashboard/index.js | 419 ++++++++++++++++++ website/index.css | 78 ++++ website/index.html | 27 ++ 7 files changed, 767 insertions(+), 4 deletions(-) rename {website => html_report}/combined_report.html (100%) create mode 100644 website/dashboard/index.css create mode 100644 website/dashboard/index.html create mode 100644 website/dashboard/index.js create mode 100644 website/index.css create mode 100644 website/index.html diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 8b009a3..ac8e4f3 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -46,9 +46,9 @@ jobs: with: name: 'Data' - - name: move data to the website folder + - name: move data to the dashboard folder run: | - mv test_output.csv dashboard_website/ + mv test_output.csv website/dashboard - name: Build documentation run: | @@ -60,12 +60,12 @@ jobs: - name: move data to the website folder run: | - mv "docs/_build/html" "dashboard_website/documentation" + mv "docs/_build/html" "website/documentation" - name: Upload docs artifact uses: actions/upload-pages-artifact@v3 with: - path: 'dashboard_website' + path: 'website' deploy: needs: build diff --git a/website/combined_report.html b/html_report/combined_report.html similarity index 100% rename from website/combined_report.html rename to html_report/combined_report.html diff --git a/website/dashboard/index.css b/website/dashboard/index.css new file mode 100644 index 0000000..db68a04 --- /dev/null +++ b/website/dashboard/index.css @@ -0,0 +1,132 @@ +:root { + --primary-color: #072A6A; + --accent-color: #62D58A; + --primary-white: #fff; + --background-color: #f4f4f9; + --shadow: rgba(0, 0, 0, 0.2); +} + +body { + margin: 0; + padding: 0; + font-family: 'Inter', 'Atkinson Hyperlegible', sans-serif; + background-color: var(--background-color); +} + +header { + padding: 1rem 2rem; + background-color: var(--primary-color); + color: var(--primary-white); +} + +.bar-title { + margin: 0; + font-size: 2rem; + font-weight: 600; +} + +.divider { + margin: 0.5rem 0; + border: none; + border-top: 2px solid var(--accent-color); +} + +main { + padding: 2rem; +} + +.filter-wrapper { + margin: 2rem; + font-size: x-large; + font-weight: bold; +} + +.filter-menu { + padding: 0.5rem 1rem; + border: none; + border-radius: 5px; + background-color: var(--primary-color); + color: var(--primary-white); + font-size: 1rem; + cursor: pointer; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.filter-menu:hover { + background-color: var(--accent-color); +} + +.chart-card { + padding: 2rem; + border-radius: 1rem; + box-shadow: 0px 8px 16px var(--shadow); + background-color: var(--primary-white); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.chart-card:hover { + transform: translateY(-5px); + box-shadow: 0px 12px 24px var(--shadow); +} + +.slider-container { + display: flex; + align-items: center; + margin: 20px 0; + width: 30%; +} +.slider-label { + margin-right: 10px; + font-weight: bold; + color: #333; +} +.slider { + margin: 0 10px; + flex-grow: 1; + height: 16px; + background: #ddd; + outline: none; + opacity: 0.9; + transition: opacity 1.2s; +} +.slider:hover { + opacity: 1; + width: 25px; + height: 25px; + cursor: pointer; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); +} + +.slider-value { + margin-left: 10px; + font-weight: bold; + color: #333; +} + +.spinner { + border: 8px solid rgba(0, 0, 0, 0.1); + width: 76px; + height: 76px; + border-radius: 50%; + border-top-color: #3498db; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.hidden { + display: none; +} diff --git a/website/dashboard/index.html b/website/dashboard/index.html new file mode 100644 index 0000000..61765a6 --- /dev/null +++ b/website/dashboard/index.html @@ -0,0 +1,107 @@ + + + + + + IVIM MRI Algorithm Fitting Dashboard + + + + + + + + +
+ +
+

IVIM MRI Algorithm Fitting Dashboard

+
+
+
+
+ + + + + + + + + +
+ Upper Range: + 0 + + + + 2 +
+
+ Lower Range: + 0 + + + + 2 +
+
+
+ +
+ + + +
+ + + + + + + + + +
+ Upper Range: + + + + 2 +
+
+ Lower Range: + 0 + + + + 2 +
+
+
+ +
+ + +
+
+ + diff --git a/website/dashboard/index.js b/website/dashboard/index.js new file mode 100644 index 0000000..9810dc7 --- /dev/null +++ b/website/dashboard/index.js @@ -0,0 +1,419 @@ +document.addEventListener('DOMContentLoaded', function() { + let data; + let selectedAlgorithm = 'ETP_SRI_LinearFitting'; + let selectedSNR = '10'; + let selectedType = 'D_fitted'; + let selectedRange = 10; + let selectedRangeLower = 2; + let selectedRegion = 'Liver'; + let selectedSNRRegion = '10'; + let selectedTypeRegion = 'D_fitted'; + let selectedRangeRegion = 10; + let selectedRangeLowerRegion = 2; + const type = { + "D_fitted": "Diffusion", + "Dp_fitted": "Perfusion", + "f_fitted": "Perfusion Fraction" + }; + + const loadingOverlay = document.getElementById('loadingOverlay'); + const mainContent = document.getElementsByTagName('main')[0]; + function showLoading() { + mainContent.classList.add('hidden'); + loadingOverlay.classList.remove('hidden'); + } + + function hideLoading() { + loadingOverlay.classList.add('hidden'); + mainContent.classList.remove('hidden') + } + + // Add event listener to algorithm select + const algorithmSelect = document.getElementById('algorithm-select'); + algorithmSelect.addEventListener('change', function(event) { + selectedAlgorithm = event.target.value; + updateRange('10') + updateRangeLower('2') + drawBoxPlot(); + }); + + // Add event listener to SNR select + const snrSelect = document.getElementById('snr-select'); + snrSelect.addEventListener('change', function(event) { + selectedSNR = event.target.value; + updateRange('10') + updateRangeLower('2') + drawBoxPlot(); + }); + + // Add event listener to type select + const typeSelect = document.getElementById('type-select'); + typeSelect.addEventListener('change', function(event) { + selectedType = event.target.value; + updateRange('10') + updateRangeLower('2') + drawBoxPlot(); + }); + + // Add event listeners to range slider and buttons + const rangeSlider = document.getElementById('range-slider'); + const rangeValue = document.getElementById('range-value'); + const decrementRange = document.getElementById('decrement-range'); + const incrementRange = document.getElementById('increment-range'); + function updateRange(value) { + selectedRange = value; + //rangeValue.textContent = value; + rangeSlider.value = value + drawBoxPlot(); + } + + rangeSlider.addEventListener('input', function(event) { + updateRange(event.target.value); + }); + + decrementRange.addEventListener('click', function() { + let newValue = parseInt(rangeSlider.value) - 2; + if (newValue >= 2) { + rangeSlider.value = newValue; + updateRange(newValue); + } + }); + + incrementRange.addEventListener('click', function() { + let newValue = parseInt(rangeSlider.value) + 2; + if (newValue <= rangeSlider.max) { + rangeSlider.value = newValue; + updateRange(newValue); + } + }); + + // Add event listeners to range slider and buttons + const rangeSliderLower = document.getElementById('lower-range-slider'); + const rangeValueLower = document.getElementById('lower-range-value'); + const decrementRangeLower = document.getElementById('decrement-lower-range'); + const incrementRangeLower = document.getElementById('increment-lower-range'); + function updateRangeLower(value) { + selectedRangeLower = value; + //rangeValue.textContent = value; + rangeSliderLower.value = value + drawBoxPlot(); + } + + rangeSliderLower.addEventListener('input', function(event) { + updateRangeLower(event.target.value); + }); + + decrementRangeLower.addEventListener('click', function() { + let newValue = parseInt(rangeSlider.value) - 2; + if (newValue >= 2) { + rangeSliderLower.value = newValue; + updateRangeLower(newValue); + } + }); + + incrementRangeLower.addEventListener('click', function() { + let newValue = parseInt(rangeSliderLower.value) + 2; + if (newValue <= rangeSliderLower.max) { + rangeSliderLower.value = newValue; + updateRangeLower(newValue); + } + }); + + // Add event listener to region select + const regionSelect = document.getElementById('region-select'); + regionSelect.addEventListener('change', function(event) { + selectedRegion = event.target.value; + updateRangeRegion('10') + updateRangeLowerRegion('2') + drawRegionBoxPlot(); + }); + + // Add event listener to SNR select + const snrRegionSelect = document.getElementById('snr-region-select'); + snrRegionSelect.addEventListener('change', function(event) { + selectedSNRRegion = event.target.value; + updateRangeRegion('10') + updateRangeLowerRegion('2') + drawRegionBoxPlot(); + }); + + // Add event listener to type select + const typeRegionSelect = document.getElementById('type-region-select'); + typeRegionSelect.addEventListener('change', function(event) { + selectedTypeRegion = event.target.value; + updateRangeRegion('10') + updateRangeLowerRegion('2') + drawRegionBoxPlot(); + }); + + // Add event listeners to range region slider and buttons + const rangeSliderRegion = document.getElementById('range-slider-region'); + const rangeValueRegion = document.getElementById('range-value-region'); + const decrementRangeRegion = document.getElementById('decrement-range-region'); + const incrementRangeRegion = document.getElementById('increment-range-region'); + + function updateRangeRegion(value) { + selectedRangeRegion = value; + //rangeValueRegion.textContent = value; + rangeSliderRegion.value = value + drawRegionBoxPlot(); + } + + rangeSliderRegion.addEventListener('input', function(event) { + updateRangeRegion(event.target.value); + }); + + decrementRangeRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderRegion.value) - 2; + if (newValue >= 2) { + rangeSliderRegion.value = newValue; + updateRangeRegion(newValue); + } + }); + + incrementRangeRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderRegion.value) + 2; + if (newValue <= rangeSliderRegion.max) { + rangeSliderRegion.value = newValue; + updateRangeRegion(newValue); + } + }); + + // Add event listeners to range slider and buttons for region + const rangeSliderLowerRegion = document.getElementById('lower-range-slider-region'); + const rangeValueLowerRegion = document.getElementById('lower-range-value-region'); + const decrementRangeLowerRegion = document.getElementById('decrement-lower-range-region'); + const incrementRangeLowerRegion = document.getElementById('increment-lower-range-region'); + function updateRangeLowerRegion(value) { + selectedRangeLowerRegion = value; + //rangeValue.textContent = value; + rangeSliderLowerRegion.value = value + drawRegionBoxPlot(); + } + + rangeSliderLowerRegion.addEventListener('input', function(event) { + updateRangeLowerRegion(event.target.value); + }); + + decrementRangeLowerRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderRegion.value) - 2; + if (newValue >= 2) { + rangeSliderLowerRegion.value = newValue; + updateRangeLowerRegion(newValue); + } + }); + + incrementRangeLowerRegion.addEventListener('click', function() { + let newValue = parseInt(rangeSliderLowerRegion.value) + 2; + if (newValue <= rangeSliderLowerRegion.max) { + rangeSliderLowerRegion.value = newValue; + updateRangeLowerRegion(newValue); + } + }); + + showLoading(); + + Papa.parse('test_output.csv', { + download: true, + header: true, + complete: results => { + data = results; + hideLoading(); + populateOptions(data); + drawBoxPlot(); + drawRegionBoxPlot(); + + } + }); + + function populateOptions(data) { + + //-----Algorithms options------ + const AlgorithmsSet = new Set(data.data.map(obj => obj.Algorithm)); + const algorithms = Array.from(AlgorithmsSet); + const algorithmSelect = document.getElementById('algorithm-select'); + algorithms.forEach(algorithm => { + let option = document.createElement('option'); + option.value = algorithm; + option.textContent = algorithm; + algorithmSelect.appendChild(option); + }); + + //-----Regions options------ + const regionsSet = new Set(data.data.map(obj => obj.Region)); + const regions = Array.from(regionsSet); + const regionSelect = document.getElementById('region-select'); + regions.forEach(region => { + let option = document.createElement('option'); + option.value = region; + option.textContent = region; + regionSelect.appendChild(option); + }); + + //-----SNR options for algorithms and regions------ + const snrSet = new Set(data.data.map(obj => obj.SNR)); + const snr = Array.from(snrSet); + console.log(snr) + const snrSelect = document.getElementById('snr-select'); + const snrRegionSelect = document.getElementById('snr-region-select'); + snr.forEach(snrValue => { + let option = document.createElement('option'); + option.value = snrValue; + option.textContent = snrValue; + snrSelect.appendChild(option); + }); + snr.forEach(snrValue => { + let option = document.createElement('option'); + option.value = snrValue; + option.textContent = snrValue; + snrRegionSelect.appendChild(option); + }); + + } + + function drawBoxPlot() { + let jsonData = data.data.filter(obj => obj.Algorithm === selectedAlgorithm); + + const allD_fittedValues = jsonData + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType]); + + const maxValue = Math.max(...allD_fittedValues); + const minValue = Math.min(...allD_fittedValues); + const groundTruthList = jsonData + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType.slice(0, -7)]); + let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) + let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) + if (minValue < 0) { + rangeValueLower.textContent = minValue.toFixed(4) + } + else { + rangeValueLower.textContent = '0.0000' + selectedRangeLower = 0; + } + rangeValue.textContent = maxValue.toFixed(4) + rangeSlider.max = Math.round(estimatedRange / 2) * 2 + rangeSliderLower.max = Math.round(estimatedRangeLower / 2) * 2 + let plots = []; + const regions = new Set(jsonData.map(obj => obj.Region)); + const uniqueRegionsArray = Array.from(regions); + uniqueRegionsArray.forEach(region => { + const D_fittedValues = jsonData + .filter(obj => obj.Region === region) + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType]); + var plot = { + y: D_fittedValues, + type: 'box', + name: region, + marker: { + outliercolor: 'white', + }, + boxpoints: 'Outliers' + }; + plots.push(plot); + const groundTruth = jsonData + .filter(obj => obj.Region === region) + .filter(obj => obj.SNR === selectedSNR) + .map(obj => obj[selectedType.slice(0, -7)]); + var constantPoint = { + x: [region], + y: groundTruth, + type: 'scatter', + name: region + ' ground truth', + mode: 'markers', + marker: { + color: 'black', + size: 10 + }, + }; + plots.push(constantPoint); + }); + + var layout = { + title: `${type[selectedType]} Box Plots for ${selectedAlgorithm} algorithm with ${selectedSNR} SNR`, + yaxis: { + autorange: false, + range: [-Math.min(...groundTruthList) * selectedRangeLower, Math.min(...groundTruthList) * selectedRange], + type: 'linear' + } + }; + + Plotly.newPlot('myDiv', plots, layout); + } + + function drawRegionBoxPlot() { + let jsonData = data.data.filter(obj => obj.Region === selectedRegion); + + const allD_fittedValues = jsonData + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion]); + + const maxValue = Math.max(...allD_fittedValues); + const minValue = Math.min(...allD_fittedValues); + + const groundTruthList = jsonData + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion.slice(0, -7)]); + let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) + let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) + if (minValue < 0) { + rangeValueLowerRegion.textContent = minValue.toFixed(4) + } + else { + rangeValueLowerRegion.textContent = '0.0000' + selectedRangeLowerRegion = 0; + } + rangeValueRegion.textContent = maxValue.toFixed(4) + rangeSliderRegion.max = Math.round(estimatedRange / 2) * 2 + rangeSliderLowerRegion.max = Math.round(estimatedRangeLower / 2) * 2 + + let plots = []; + const algorithms = new Set(jsonData.map(obj => obj.Algorithm)); + const uniqueAlgorithmsArray = Array.from(algorithms); + uniqueAlgorithmsArray.forEach(algorithm => { + const D_fittedValues = jsonData + .filter(obj => obj.Algorithm === algorithm) + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion]); + var plot = { + y: D_fittedValues, + type: 'box', + name: algorithm, + marker: { + outliercolor: 'white', + }, + boxpoints: 'Outliers' + }; + plots.push(plot); + const groundTruth = jsonData + .filter(obj => obj.Algorithm === algorithm) + .filter(obj => obj.SNR === selectedSNRRegion) + .map(obj => obj[selectedTypeRegion.slice(0, -7)]); + var constantPoint = { + x: [algorithm], + y: groundTruth, + type: 'scatter', + name: algorithm + ' ground truth', + mode: 'markers', + marker: { + color: 'black', + size: 10 + }, + }; + plots.push(constantPoint); + }); + + var layout = { + title: `${type[selectedTypeRegion]} Box Plots for ${selectedRegion} region with ${selectedSNR} SNR`, + yaxis: { + autorange: false, + range: [-Math.min(...groundTruthList) * selectedRangeLowerRegion, Math.min(...groundTruthList) * selectedRangeRegion], + type: 'linear' + } + }; + + Plotly.newPlot('regionDiv', plots, layout); + } +}); diff --git a/website/index.css b/website/index.css new file mode 100644 index 0000000..95317e1 --- /dev/null +++ b/website/index.css @@ -0,0 +1,78 @@ +main { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + height: 80vh; + background-color: #f0f0f0; +} +:root { + --primary-color: #072A6A; + --accent-color: #62D58A; + --primary-white: #fff; + --background-color: #f4f4f9; + --shadow: rgba(0, 0, 0, 0.2); + } + + body { + margin: 0; + padding: 0; + font-family: 'Inter', 'Atkinson Hyperlegible', sans-serif; + background-color: #f0f0f0; + } + +.container { + text-align: center; +} + +h1 { + margin-bottom: 20px; +} + +.cards { + display: flex; + gap: 20px; + justify-content: center; +} + +.card { + background-color: white; + border: 1px solid #ccc; + border-radius: 8px; + padding: 20px; + text-align: center; + text-decoration: none; + color: black; + width: 250px; + height: 150px; + transition: transform 0.2s, box-shadow 0.2s; +} + +.card:hover { + transform: scale(1.05); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.card h2 { + margin: 0; +} + +header { + padding: 1rem 2rem; + background-color: var(--primary-color); + color: var(--primary-white); + } + +.bar-title { + margin: 0; + font-size: 2rem; + font-weight: 600; + } + + .divider { + margin: 0.5rem 0; + border: none; + border-top: 2px solid var(--accent-color); + } diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..e7d7ebd --- /dev/null +++ b/website/index.html @@ -0,0 +1,27 @@ + + + + + + OSIPI Taskforce 2.4 IVIM MRI + + + +
+

Welcome to OSIPI Taskforce 2.4 IVIM MRI

+
+
+
+ +
+ + From e40577eab47849fc416c4a1e4085d3c27008d357 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Fri, 19 Jul 2024 10:24:20 +0300 Subject: [PATCH 09/11] delete old folder --- dashboard_website/index.css | 132 ----------- dashboard_website/index.html | 107 --------- dashboard_website/index.js | 419 ----------------------------------- 3 files changed, 658 deletions(-) delete mode 100644 dashboard_website/index.css delete mode 100644 dashboard_website/index.html delete mode 100644 dashboard_website/index.js diff --git a/dashboard_website/index.css b/dashboard_website/index.css deleted file mode 100644 index db68a04..0000000 --- a/dashboard_website/index.css +++ /dev/null @@ -1,132 +0,0 @@ -:root { - --primary-color: #072A6A; - --accent-color: #62D58A; - --primary-white: #fff; - --background-color: #f4f4f9; - --shadow: rgba(0, 0, 0, 0.2); -} - -body { - margin: 0; - padding: 0; - font-family: 'Inter', 'Atkinson Hyperlegible', sans-serif; - background-color: var(--background-color); -} - -header { - padding: 1rem 2rem; - background-color: var(--primary-color); - color: var(--primary-white); -} - -.bar-title { - margin: 0; - font-size: 2rem; - font-weight: 600; -} - -.divider { - margin: 0.5rem 0; - border: none; - border-top: 2px solid var(--accent-color); -} - -main { - padding: 2rem; -} - -.filter-wrapper { - margin: 2rem; - font-size: x-large; - font-weight: bold; -} - -.filter-menu { - padding: 0.5rem 1rem; - border: none; - border-radius: 5px; - background-color: var(--primary-color); - color: var(--primary-white); - font-size: 1rem; - cursor: pointer; - transition: background-color 0.3s ease, color 0.3s ease; -} - -.filter-menu:hover { - background-color: var(--accent-color); -} - -.chart-card { - padding: 2rem; - border-radius: 1rem; - box-shadow: 0px 8px 16px var(--shadow); - background-color: var(--primary-white); - transition: transform 0.3s ease, box-shadow 0.3s ease; -} - -.chart-card:hover { - transform: translateY(-5px); - box-shadow: 0px 12px 24px var(--shadow); -} - -.slider-container { - display: flex; - align-items: center; - margin: 20px 0; - width: 30%; -} -.slider-label { - margin-right: 10px; - font-weight: bold; - color: #333; -} -.slider { - margin: 0 10px; - flex-grow: 1; - height: 16px; - background: #ddd; - outline: none; - opacity: 0.9; - transition: opacity 1.2s; -} -.slider:hover { - opacity: 1; - width: 25px; - height: 25px; - cursor: pointer; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); -} - -.slider-value { - margin-left: 10px; - font-weight: bold; - color: #333; -} - -.spinner { - border: 8px solid rgba(0, 0, 0, 0.1); - width: 76px; - height: 76px; - border-radius: 50%; - border-top-color: #3498db; - animation: spin 1s ease-in-out infinite; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -.loading-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; -} - -.hidden { - display: none; -} diff --git a/dashboard_website/index.html b/dashboard_website/index.html deleted file mode 100644 index 61765a6..0000000 --- a/dashboard_website/index.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - IVIM MRI Algorithm Fitting Dashboard - - - - - - - - -
- -
-

IVIM MRI Algorithm Fitting Dashboard

-
-
-
-
- - - - - - - - - -
- Upper Range: - 0 - - - - 2 -
-
- Lower Range: - 0 - - - - 2 -
-
-
- -
- - - -
- - - - - - - - - -
- Upper Range: - - - - 2 -
-
- Lower Range: - 0 - - - - 2 -
-
-
- -
- - -
-
- - diff --git a/dashboard_website/index.js b/dashboard_website/index.js deleted file mode 100644 index 9810dc7..0000000 --- a/dashboard_website/index.js +++ /dev/null @@ -1,419 +0,0 @@ -document.addEventListener('DOMContentLoaded', function() { - let data; - let selectedAlgorithm = 'ETP_SRI_LinearFitting'; - let selectedSNR = '10'; - let selectedType = 'D_fitted'; - let selectedRange = 10; - let selectedRangeLower = 2; - let selectedRegion = 'Liver'; - let selectedSNRRegion = '10'; - let selectedTypeRegion = 'D_fitted'; - let selectedRangeRegion = 10; - let selectedRangeLowerRegion = 2; - const type = { - "D_fitted": "Diffusion", - "Dp_fitted": "Perfusion", - "f_fitted": "Perfusion Fraction" - }; - - const loadingOverlay = document.getElementById('loadingOverlay'); - const mainContent = document.getElementsByTagName('main')[0]; - function showLoading() { - mainContent.classList.add('hidden'); - loadingOverlay.classList.remove('hidden'); - } - - function hideLoading() { - loadingOverlay.classList.add('hidden'); - mainContent.classList.remove('hidden') - } - - // Add event listener to algorithm select - const algorithmSelect = document.getElementById('algorithm-select'); - algorithmSelect.addEventListener('change', function(event) { - selectedAlgorithm = event.target.value; - updateRange('10') - updateRangeLower('2') - drawBoxPlot(); - }); - - // Add event listener to SNR select - const snrSelect = document.getElementById('snr-select'); - snrSelect.addEventListener('change', function(event) { - selectedSNR = event.target.value; - updateRange('10') - updateRangeLower('2') - drawBoxPlot(); - }); - - // Add event listener to type select - const typeSelect = document.getElementById('type-select'); - typeSelect.addEventListener('change', function(event) { - selectedType = event.target.value; - updateRange('10') - updateRangeLower('2') - drawBoxPlot(); - }); - - // Add event listeners to range slider and buttons - const rangeSlider = document.getElementById('range-slider'); - const rangeValue = document.getElementById('range-value'); - const decrementRange = document.getElementById('decrement-range'); - const incrementRange = document.getElementById('increment-range'); - function updateRange(value) { - selectedRange = value; - //rangeValue.textContent = value; - rangeSlider.value = value - drawBoxPlot(); - } - - rangeSlider.addEventListener('input', function(event) { - updateRange(event.target.value); - }); - - decrementRange.addEventListener('click', function() { - let newValue = parseInt(rangeSlider.value) - 2; - if (newValue >= 2) { - rangeSlider.value = newValue; - updateRange(newValue); - } - }); - - incrementRange.addEventListener('click', function() { - let newValue = parseInt(rangeSlider.value) + 2; - if (newValue <= rangeSlider.max) { - rangeSlider.value = newValue; - updateRange(newValue); - } - }); - - // Add event listeners to range slider and buttons - const rangeSliderLower = document.getElementById('lower-range-slider'); - const rangeValueLower = document.getElementById('lower-range-value'); - const decrementRangeLower = document.getElementById('decrement-lower-range'); - const incrementRangeLower = document.getElementById('increment-lower-range'); - function updateRangeLower(value) { - selectedRangeLower = value; - //rangeValue.textContent = value; - rangeSliderLower.value = value - drawBoxPlot(); - } - - rangeSliderLower.addEventListener('input', function(event) { - updateRangeLower(event.target.value); - }); - - decrementRangeLower.addEventListener('click', function() { - let newValue = parseInt(rangeSlider.value) - 2; - if (newValue >= 2) { - rangeSliderLower.value = newValue; - updateRangeLower(newValue); - } - }); - - incrementRangeLower.addEventListener('click', function() { - let newValue = parseInt(rangeSliderLower.value) + 2; - if (newValue <= rangeSliderLower.max) { - rangeSliderLower.value = newValue; - updateRangeLower(newValue); - } - }); - - // Add event listener to region select - const regionSelect = document.getElementById('region-select'); - regionSelect.addEventListener('change', function(event) { - selectedRegion = event.target.value; - updateRangeRegion('10') - updateRangeLowerRegion('2') - drawRegionBoxPlot(); - }); - - // Add event listener to SNR select - const snrRegionSelect = document.getElementById('snr-region-select'); - snrRegionSelect.addEventListener('change', function(event) { - selectedSNRRegion = event.target.value; - updateRangeRegion('10') - updateRangeLowerRegion('2') - drawRegionBoxPlot(); - }); - - // Add event listener to type select - const typeRegionSelect = document.getElementById('type-region-select'); - typeRegionSelect.addEventListener('change', function(event) { - selectedTypeRegion = event.target.value; - updateRangeRegion('10') - updateRangeLowerRegion('2') - drawRegionBoxPlot(); - }); - - // Add event listeners to range region slider and buttons - const rangeSliderRegion = document.getElementById('range-slider-region'); - const rangeValueRegion = document.getElementById('range-value-region'); - const decrementRangeRegion = document.getElementById('decrement-range-region'); - const incrementRangeRegion = document.getElementById('increment-range-region'); - - function updateRangeRegion(value) { - selectedRangeRegion = value; - //rangeValueRegion.textContent = value; - rangeSliderRegion.value = value - drawRegionBoxPlot(); - } - - rangeSliderRegion.addEventListener('input', function(event) { - updateRangeRegion(event.target.value); - }); - - decrementRangeRegion.addEventListener('click', function() { - let newValue = parseInt(rangeSliderRegion.value) - 2; - if (newValue >= 2) { - rangeSliderRegion.value = newValue; - updateRangeRegion(newValue); - } - }); - - incrementRangeRegion.addEventListener('click', function() { - let newValue = parseInt(rangeSliderRegion.value) + 2; - if (newValue <= rangeSliderRegion.max) { - rangeSliderRegion.value = newValue; - updateRangeRegion(newValue); - } - }); - - // Add event listeners to range slider and buttons for region - const rangeSliderLowerRegion = document.getElementById('lower-range-slider-region'); - const rangeValueLowerRegion = document.getElementById('lower-range-value-region'); - const decrementRangeLowerRegion = document.getElementById('decrement-lower-range-region'); - const incrementRangeLowerRegion = document.getElementById('increment-lower-range-region'); - function updateRangeLowerRegion(value) { - selectedRangeLowerRegion = value; - //rangeValue.textContent = value; - rangeSliderLowerRegion.value = value - drawRegionBoxPlot(); - } - - rangeSliderLowerRegion.addEventListener('input', function(event) { - updateRangeLowerRegion(event.target.value); - }); - - decrementRangeLowerRegion.addEventListener('click', function() { - let newValue = parseInt(rangeSliderRegion.value) - 2; - if (newValue >= 2) { - rangeSliderLowerRegion.value = newValue; - updateRangeLowerRegion(newValue); - } - }); - - incrementRangeLowerRegion.addEventListener('click', function() { - let newValue = parseInt(rangeSliderLowerRegion.value) + 2; - if (newValue <= rangeSliderLowerRegion.max) { - rangeSliderLowerRegion.value = newValue; - updateRangeLowerRegion(newValue); - } - }); - - showLoading(); - - Papa.parse('test_output.csv', { - download: true, - header: true, - complete: results => { - data = results; - hideLoading(); - populateOptions(data); - drawBoxPlot(); - drawRegionBoxPlot(); - - } - }); - - function populateOptions(data) { - - //-----Algorithms options------ - const AlgorithmsSet = new Set(data.data.map(obj => obj.Algorithm)); - const algorithms = Array.from(AlgorithmsSet); - const algorithmSelect = document.getElementById('algorithm-select'); - algorithms.forEach(algorithm => { - let option = document.createElement('option'); - option.value = algorithm; - option.textContent = algorithm; - algorithmSelect.appendChild(option); - }); - - //-----Regions options------ - const regionsSet = new Set(data.data.map(obj => obj.Region)); - const regions = Array.from(regionsSet); - const regionSelect = document.getElementById('region-select'); - regions.forEach(region => { - let option = document.createElement('option'); - option.value = region; - option.textContent = region; - regionSelect.appendChild(option); - }); - - //-----SNR options for algorithms and regions------ - const snrSet = new Set(data.data.map(obj => obj.SNR)); - const snr = Array.from(snrSet); - console.log(snr) - const snrSelect = document.getElementById('snr-select'); - const snrRegionSelect = document.getElementById('snr-region-select'); - snr.forEach(snrValue => { - let option = document.createElement('option'); - option.value = snrValue; - option.textContent = snrValue; - snrSelect.appendChild(option); - }); - snr.forEach(snrValue => { - let option = document.createElement('option'); - option.value = snrValue; - option.textContent = snrValue; - snrRegionSelect.appendChild(option); - }); - - } - - function drawBoxPlot() { - let jsonData = data.data.filter(obj => obj.Algorithm === selectedAlgorithm); - - const allD_fittedValues = jsonData - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType]); - - const maxValue = Math.max(...allD_fittedValues); - const minValue = Math.min(...allD_fittedValues); - const groundTruthList = jsonData - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType.slice(0, -7)]); - let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) - let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) - if (minValue < 0) { - rangeValueLower.textContent = minValue.toFixed(4) - } - else { - rangeValueLower.textContent = '0.0000' - selectedRangeLower = 0; - } - rangeValue.textContent = maxValue.toFixed(4) - rangeSlider.max = Math.round(estimatedRange / 2) * 2 - rangeSliderLower.max = Math.round(estimatedRangeLower / 2) * 2 - let plots = []; - const regions = new Set(jsonData.map(obj => obj.Region)); - const uniqueRegionsArray = Array.from(regions); - uniqueRegionsArray.forEach(region => { - const D_fittedValues = jsonData - .filter(obj => obj.Region === region) - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType]); - var plot = { - y: D_fittedValues, - type: 'box', - name: region, - marker: { - outliercolor: 'white', - }, - boxpoints: 'Outliers' - }; - plots.push(plot); - const groundTruth = jsonData - .filter(obj => obj.Region === region) - .filter(obj => obj.SNR === selectedSNR) - .map(obj => obj[selectedType.slice(0, -7)]); - var constantPoint = { - x: [region], - y: groundTruth, - type: 'scatter', - name: region + ' ground truth', - mode: 'markers', - marker: { - color: 'black', - size: 10 - }, - }; - plots.push(constantPoint); - }); - - var layout = { - title: `${type[selectedType]} Box Plots for ${selectedAlgorithm} algorithm with ${selectedSNR} SNR`, - yaxis: { - autorange: false, - range: [-Math.min(...groundTruthList) * selectedRangeLower, Math.min(...groundTruthList) * selectedRange], - type: 'linear' - } - }; - - Plotly.newPlot('myDiv', plots, layout); - } - - function drawRegionBoxPlot() { - let jsonData = data.data.filter(obj => obj.Region === selectedRegion); - - const allD_fittedValues = jsonData - .filter(obj => obj.SNR === selectedSNRRegion) - .map(obj => obj[selectedTypeRegion]); - - const maxValue = Math.max(...allD_fittedValues); - const minValue = Math.min(...allD_fittedValues); - - const groundTruthList = jsonData - .filter(obj => obj.SNR === selectedSNRRegion) - .map(obj => obj[selectedTypeRegion.slice(0, -7)]); - let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) - let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) - if (minValue < 0) { - rangeValueLowerRegion.textContent = minValue.toFixed(4) - } - else { - rangeValueLowerRegion.textContent = '0.0000' - selectedRangeLowerRegion = 0; - } - rangeValueRegion.textContent = maxValue.toFixed(4) - rangeSliderRegion.max = Math.round(estimatedRange / 2) * 2 - rangeSliderLowerRegion.max = Math.round(estimatedRangeLower / 2) * 2 - - let plots = []; - const algorithms = new Set(jsonData.map(obj => obj.Algorithm)); - const uniqueAlgorithmsArray = Array.from(algorithms); - uniqueAlgorithmsArray.forEach(algorithm => { - const D_fittedValues = jsonData - .filter(obj => obj.Algorithm === algorithm) - .filter(obj => obj.SNR === selectedSNRRegion) - .map(obj => obj[selectedTypeRegion]); - var plot = { - y: D_fittedValues, - type: 'box', - name: algorithm, - marker: { - outliercolor: 'white', - }, - boxpoints: 'Outliers' - }; - plots.push(plot); - const groundTruth = jsonData - .filter(obj => obj.Algorithm === algorithm) - .filter(obj => obj.SNR === selectedSNRRegion) - .map(obj => obj[selectedTypeRegion.slice(0, -7)]); - var constantPoint = { - x: [algorithm], - y: groundTruth, - type: 'scatter', - name: algorithm + ' ground truth', - mode: 'markers', - marker: { - color: 'black', - size: 10 - }, - }; - plots.push(constantPoint); - }); - - var layout = { - title: `${type[selectedTypeRegion]} Box Plots for ${selectedRegion} region with ${selectedSNR} SNR`, - yaxis: { - autorange: false, - range: [-Math.min(...groundTruthList) * selectedRangeLowerRegion, Math.min(...groundTruthList) * selectedRangeRegion], - type: 'linear' - } - }; - - Plotly.newPlot('regionDiv', plots, layout); - } -}); From 5961a4b63f78255c8653388a91081b8e52965003 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Fri, 19 Jul 2024 12:57:12 +0300 Subject: [PATCH 10/11] Fix range issues: dquote> - hide the slider when the there is no range. dquote> - display current value instead of the max. --- website/dashboard/index.css | 3 +-- website/dashboard/index.html | 15 ++++++--------- website/dashboard/index.js | 32 ++++++++++++++------------------ 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/website/dashboard/index.css b/website/dashboard/index.css index db68a04..8443e1f 100644 --- a/website/dashboard/index.css +++ b/website/dashboard/index.css @@ -91,14 +91,13 @@ main { } .slider:hover { opacity: 1; - width: 25px; height: 25px; cursor: pointer; box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); } .slider-value { - margin-left: 10px; + margin: 0 10px; font-weight: bold; color: #333; } diff --git a/website/dashboard/index.html b/website/dashboard/index.html index 61765a6..eefdcaa 100644 --- a/website/dashboard/index.html +++ b/website/dashboard/index.html @@ -41,18 +41,16 @@

IVIM MRI Algorithm Fitting Dashboard

Upper Range: - 0 2
-
+
Lower Range: - 0 - + - + 2
@@ -87,12 +85,11 @@

IVIM MRI Algorithm Fitting Dashboard

2
-
+
Lower Range: - 0 - + - + 2
diff --git a/website/dashboard/index.js b/website/dashboard/index.js index 9810dc7..3d0a503 100644 --- a/website/dashboard/index.js +++ b/website/dashboard/index.js @@ -81,10 +81,8 @@ document.addEventListener('DOMContentLoaded', function() { incrementRange.addEventListener('click', function() { let newValue = parseInt(rangeSlider.value) + 2; - if (newValue <= rangeSlider.max) { rangeSlider.value = newValue; updateRange(newValue); - } }); // Add event listeners to range slider and buttons @@ -113,10 +111,8 @@ document.addEventListener('DOMContentLoaded', function() { incrementRangeLower.addEventListener('click', function() { let newValue = parseInt(rangeSliderLower.value) + 2; - if (newValue <= rangeSliderLower.max) { - rangeSliderLower.value = newValue; - updateRangeLower(newValue); - } + rangeSliderLower.value = newValue; + updateRangeLower(newValue); }); // Add event listener to region select @@ -173,10 +169,8 @@ document.addEventListener('DOMContentLoaded', function() { incrementRangeRegion.addEventListener('click', function() { let newValue = parseInt(rangeSliderRegion.value) + 2; - if (newValue <= rangeSliderRegion.max) { - rangeSliderRegion.value = newValue; - updateRangeRegion(newValue); - } + rangeSliderRegion.value = newValue; + updateRangeRegion(newValue); }); // Add event listeners to range slider and buttons for region @@ -205,10 +199,8 @@ document.addEventListener('DOMContentLoaded', function() { incrementRangeLowerRegion.addEventListener('click', function() { let newValue = parseInt(rangeSliderLowerRegion.value) + 2; - if (newValue <= rangeSliderLowerRegion.max) { - rangeSliderLowerRegion.value = newValue; - updateRangeLowerRegion(newValue); - } + rangeSliderLowerRegion.value = newValue; + updateRangeLowerRegion(newValue); }); showLoading(); @@ -283,16 +275,20 @@ document.addEventListener('DOMContentLoaded', function() { const groundTruthList = jsonData .filter(obj => obj.SNR === selectedSNR) .map(obj => obj[selectedType.slice(0, -7)]); - let estimatedRange = Math.abs(maxValue) / Math.min(...groundTruthList) + let estimatedRange = Math.ceil(Math.abs(maxValue) / Math.min(...groundTruthList)) let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) + currentUpperValue = (Math.min(...groundTruthList) * selectedRange).toFixed(4) + currentLowerValue = (-Math.min(...groundTruthList) * selectedRangeLower).toFixed(4) if (minValue < 0) { - rangeValueLower.textContent = minValue.toFixed(4) + rangeValueLower.textContent = currentLowerValue + document.getElementById('lower-slider').style.visibility = 'visible'; } else { + document.getElementById('lower-slider').style.visibility = 'hidden'; rangeValueLower.textContent = '0.0000' selectedRangeLower = 0; } - rangeValue.textContent = maxValue.toFixed(4) + rangeValue.textContent = currentUpperValue rangeSlider.max = Math.round(estimatedRange / 2) * 2 rangeSliderLower.max = Math.round(estimatedRangeLower / 2) * 2 let plots = []; @@ -335,7 +331,7 @@ document.addEventListener('DOMContentLoaded', function() { title: `${type[selectedType]} Box Plots for ${selectedAlgorithm} algorithm with ${selectedSNR} SNR`, yaxis: { autorange: false, - range: [-Math.min(...groundTruthList) * selectedRangeLower, Math.min(...groundTruthList) * selectedRange], + range: [currentLowerValue, currentUpperValue], type: 'linear' } }; From cae60b116138f6dab12dba777c2fe3e2fab88279 Mon Sep 17 00:00:00 2001 From: AhmedBasem Date: Fri, 19 Jul 2024 14:47:23 +0300 Subject: [PATCH 11/11] hide the slider when range is zero for region plots. --- website/dashboard/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/dashboard/index.js b/website/dashboard/index.js index 3d0a503..f0166ae 100644 --- a/website/dashboard/index.js +++ b/website/dashboard/index.js @@ -356,8 +356,10 @@ document.addEventListener('DOMContentLoaded', function() { let estimatedRangeLower = Math.abs(minValue) / Math.min(...groundTruthList) if (minValue < 0) { rangeValueLowerRegion.textContent = minValue.toFixed(4) + document.getElementById('region-lower-slider').style.visibility = 'visible'; } else { + document.getElementById('region-lower-slider').style.visibility = 'hidden'; rangeValueLowerRegion.textContent = '0.0000' selectedRangeLowerRegion = 0; }