From 83a02ff667fa05cc5ed17f1171232224fba23c05 Mon Sep 17 00:00:00 2001
From: m34-singh <milan2.singh@live.uwe.ac.uk>
Date: Tue, 29 Apr 2025 16:01:01 +0000
Subject: [PATCH] initial commit

---
 Digital systems project/templates/index.html | 852 +++++++++++++++++++
 1 file changed, 852 insertions(+)
 create mode 100644 Digital systems project/templates/index.html

diff --git a/Digital systems project/templates/index.html b/Digital systems project/templates/index.html
new file mode 100644
index 0000000..9e8ef60
--- /dev/null
+++ b/Digital systems project/templates/index.html	
@@ -0,0 +1,852 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>MRI Detection, Classification, and Segmentation</title>
+  <style>
+    /* Using custom 'Roboto' font */
+    @font-face {
+      font-family: 'Roboto';
+      src: url('static/fonts/Roboto/Roboto-Regular.ttf') format('truetype');
+    }
+
+    /* Zero out margin/padding and ensure box-sizing for consistency */
+    * {
+      font-family: 'Roboto', sans-serif;
+      box-sizing: border-box;
+      margin: 0;
+      padding: 0;
+    }
+
+    /* Body takes a dark background and white text for contrast */
+    body {
+      font-family: 'Roboto', sans-serif;
+      background-color: #0c0e1a;
+      color: #ffffff;
+      margin: 0;
+      padding: 0;
+    }
+
+    /* Header region at the top, with some spacing and bottom border */
+    header {
+      font-family: 'Roboto', sans-serif;
+      background-color: #0c0e1a;
+      padding: 1rem 2rem;
+      border-bottom: 1px solid #2c2c2c;
+    }
+    header h1 {
+      font-family: 'Roboto', sans-serif;
+      margin: 0;
+      font-size: 1.5rem;
+      color: #ffffff;
+      display: inline-block;
+    }
+
+    /* Nav bar with flexbox for spacing links and logout */
+    nav {
+      font-family: 'Roboto', sans-serif;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      background-color: #06070d;
+      padding: 1rem 2rem;
+      border-bottom: 1px solid #2c2c2c;
+    }
+    .nav-links a {
+      font-family: 'Roboto', sans-serif;
+      margin-left: 1.5rem;
+      text-decoration: none;
+      color: #ffffff;
+      font-weight: 500;
+      font-size: 0.95rem;
+    }
+    .nav-links a:hover {
+      font-family: 'Roboto', sans-serif;
+      text-decoration: underline;
+    }
+
+    /* Styling for the logout button on the right side */
+    .logout-button {
+      font-family: 'Roboto', sans-serif;
+      text-decoration: none;
+      color: #0c0e1a;
+      background: linear-gradient(to right, #a64071, #72274a);
+      padding: 0.5rem 1rem;
+      border-radius: 4px;
+      font-weight: bold;
+      font-size: 0.9rem;
+    }
+    .logout-button:hover {
+      font-family: 'Roboto', sans-serif;
+      background: linear-gradient(to right, #cd4f8c, #963361);
+    }
+
+    /* Main container holding sidebar and viewer, with some gap */
+    .container {
+      font-family: 'Roboto', sans-serif;
+      max-width: 1400px;
+      margin: 1rem auto;
+      display: flex;
+      gap: 1rem;
+      padding: 0 1rem;
+    }
+
+    /* Sidebar for file input, controls, and histogram */
+    .sidebar {
+      font-family: 'Roboto', sans-serif;
+      width: 280px;
+      background-color: #06070d;
+      border: 1px solid #2c2c2c;
+      border-radius: 6px;
+      padding: 1rem;
+      display: flex;
+      flex-direction: column;
+      gap: 1rem;
+    }
+
+    .sidebar h2 {
+      font-family: 'Roboto', sans-serif;
+      margin-top: 0;
+      font-size: 1.1rem;
+      color: #FFFFFF;
+    }
+
+    .sidebar button {
+      font-family: 'Roboto', sans-serif;
+      border: none;
+      border-radius: 4px;
+      background: linear-gradient(to right, #a64071, #72274a);
+      color: #0c0e1a;
+      font-size: 0.9rem;
+      padding: 0.6rem 0.8rem;
+      cursor: pointer;
+      font-weight: bold;
+    }
+
+    .sidebar button:hover {
+      font-family: 'Roboto', sans-serif;
+      background: linear-gradient(to right, #cd4f8c, #963361);
+    }
+
+    label {
+      font-family: 'Roboto', sans-serif;
+      display: block;
+      margin-bottom: 0.2rem;
+      font-weight: 500;
+      color: #ffffff;
+    }
+
+    input[type="file"] {
+      font-family: 'Roboto', sans-serif;
+      margin-bottom: 0.5rem;
+      background-color: #06070d;
+      color: #FFFFFF;
+      border: 1px solid #333;
+      border-radius: 4px;
+      padding: 0.3rem;
+    }
+
+    .range-controls label {
+      font-family: 'Roboto', sans-serif;
+      color: #ffffff;
+    }
+
+    .range-controls input[type="range"] {
+      font-family: 'Roboto', sans-serif;
+      width: 100%;
+    }
+
+    /* Box for the histogram with a background and border */
+    .histogram-container {
+      font-family: 'Roboto', sans-serif;
+      background: #06070d;
+      border: 1px solid #2c2c2c;
+      padding: 0.5rem;
+      border-radius: 4px;
+      text-align: center;
+    }
+
+    .histogram-container h3 {
+      font-family: 'Roboto', sans-serif;
+      margin: 0 0 0.5rem;
+      font-size: 1rem;
+      color: #FFFFFF;
+    }
+
+    .histogram-container canvas {
+      font-family: 'Roboto', sans-serif;
+      width: 100%;
+      height: 150px;
+      background: #1c1c1c;
+      display: block;
+      margin: 0 auto;
+    }
+
+    /* Viewer that displays original and processed images */
+    .viewer {
+      font-family: 'Roboto', sans-serif;
+      flex: 1;
+      background-color: #06070d;
+      border: 1px solid #2c2c2c;
+      border-radius: 6px;
+      padding: 1rem;
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      gap: 1rem;
+    }
+
+    .viewer h2 {
+      font-family: 'Roboto', sans-serif;
+      margin-top: 0;
+      font-size: 1.1rem;
+      color: #FFFFFF;
+    }
+
+    /* Layout for the two images side by side */
+    .viewer-images {
+      font-family: 'Roboto', sans-serif;
+      width: 100%;
+      display: flex;
+      gap: 1rem;
+      justify-content: space-evenly;
+      align-items: flex-start;
+      flex-wrap: wrap;
+    }
+
+    .image-wrapper {
+      font-family: 'Roboto', sans-serif;
+      position: relative;
+      max-width: 48%;
+    }
+
+    .image-wrapper img {
+      font-family: 'Roboto', sans-serif;
+      width: 100%;
+      max-height: 70vh;
+      object-fit: contain;
+      background: #000;
+      border: 2px solid #333;
+      display: none;
+    }
+
+    .annotation-canvas {
+      font-family: 'Roboto', sans-serif;
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 10;
+      pointer-events: auto;
+      cursor: crosshair;
+    }
+
+    .mask-overlay {
+      font-family: 'Roboto', sans-serif;
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 10;
+      pointer-events: none;
+    }
+
+    /* Buttons for applying various color maps */
+    .colormap-buttons {
+      font-family: 'Roboto', sans-serif;
+      display: flex;
+      gap: 0.5rem;
+      flex-wrap: wrap;
+      justify-content: center;
+    }
+
+    .colormap-buttons button {
+      font-family: 'Roboto', sans-serif;
+      padding: 0.4rem 0.6rem;
+      border: none;
+      border-radius: 4px;
+      cursor: pointer;
+      background: #a64071;
+      color: #0c0e1a;
+      font-size: 0.8rem;
+      font-weight: bold;
+    }
+
+    .colormap-buttons button:hover {
+      font-family: 'Roboto', sans-serif;
+      background: #cd4f8c;
+    }
+
+    /* Panel for classification results */
+    .analysis-result {
+      font-family: 'Roboto', sans-serif;
+      padding: 0.5rem;
+      border: 1px solid #2c2c2c;
+      border-radius: 6px;
+      background-color: #0c0e1a;
+      text-align: center;
+      font-size: 1rem;
+      color: #ffffff;
+    }
+
+    /* Footer */
+    footer {
+      font-family: 'Roboto', sans-serif;
+      text-align: center;
+      background: #0c0e1a;
+      border-top: 1px solid #2c2c2c;
+      padding: 1rem;
+      margin-top: 1rem;
+      font-size: 0.85rem;
+      color: #ffffff;
+    }
+
+    /* Overlay for showing any error messages */
+    .modal-overlay {
+      font-family: 'Roboto', sans-serif;
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background: rgba(0, 0, 0, 0.6);
+      display: none;
+      justify-content: center;
+      align-items: center;
+      z-index: 9999;
+    }
+
+    .modal-content {
+      font-family: 'Roboto', sans-serif;
+      background: #2d0e1a;
+      color: #ffffff;
+      width: 300px;
+      padding: 1.5rem;
+      border-radius: 8px;
+      text-align: center;
+      border: 2px solid #d04f5c;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
+    }
+
+    .modal-content h2 {
+      font-family: 'Roboto', sans-serif;
+      margin-top: 0;
+      margin-bottom: 0.5rem;
+      font-size: 1.2rem;
+      color: #d04f5c;
+    }
+
+    .modal-content p {
+      font-family: 'Roboto', sans-serif;
+      margin-bottom: 1rem;
+      color: #d04f5c;
+    }
+
+    .modal-content button {
+      font-family: 'Roboto', sans-serif;
+      border: none;
+      border-radius: 4px;
+      background: linear-gradient(to right, #a64071, #72274a);
+      color: #0c0e1a;
+      font-size: 0.9rem;
+      padding: 0.5rem 1rem;
+      cursor: pointer;
+      font-weight: bold;
+    }
+
+    .modal-content button:hover {
+      font-family: 'Roboto', sans-serif;
+      background: linear-gradient(to right, #cd4f8c, #963361);
+    }
+  </style>
+</head>
+<body>
+  <!-- Header area with main title -->
+  <header>
+    <h1>MRI Detection, Classification, and Segmentation</h1>
+  </header>
+
+  <!-- Navigation bar with page links -->
+  <nav>
+    <div class="nav-links">
+      <a href="/">Detection Tools</a>
+      <a href="/segmentation">Segmentation Tools</a>
+    </div>
+    <a href="/logout" class="logout-button">Logout</a>
+  </nav>
+
+  <!-- Main container with the sidebar on the left and viewer on the right -->
+  <div class="container">
+    <div class="sidebar">
+      <h2>Controls</h2>
+      <label>Select MRI (PNG/JPG)</label>
+      <input type="file" id="fileInput" accept=".png,.jpg,.jpeg" />
+      <button id="analyzeBtn">Analyse Tumor</button>
+
+      <div class="range-controls">
+        <label for="brightnessRange">Brightness</label>
+        <input type="range" id="brightnessRange" min="-100" max="100" value="0">
+        <label for="contrastRange">Contrast</label>
+        <input type="range" id="contrastRange" min="0" max="200" value="100">
+      </div>
+
+      <div class="histogram-container">
+        <h3>Histogram</h3>
+        <canvas id="histogramCanvas"></canvas>
+      </div>
+
+      <button id="exportBtn">Export Annotated Image</button>
+    </div>
+
+    <div class="viewer">
+      <h2>MRI Viewer</h2>
+      <div class="viewer-images">
+        <div class="image-wrapper" id="leftWrapper">
+          <img id="mriImage" alt="Original MRI">
+          <canvas id="annotationCanvas" class="annotation-canvas"></canvas>
+        </div>
+        <div class="image-wrapper" id="rightWrapper">
+          <img id="colorMapImage" alt="Processed/ColorMap MRI">
+          <canvas id="maskOverlay" class="mask-overlay"></canvas>
+        </div>
+      </div>
+
+      <div id="analysisResult" class="analysis-result" style="display: none;"></div>
+
+      <div class="colormap-buttons">
+        <button onclick="requestColorMap('jet')">JET</button>
+        <button onclick="requestColorMap('hot')">HOT</button>
+        <button onclick="requestColorMap('rainbow')">RAINBOW</button>
+        <button onclick="requestColorMap('bone')">BONE</button>
+        <button onclick="requestColorMap('ocean')">OCEAN</button>
+        <button onclick="requestColorMap('winter')">WINTER</button>
+        <button onclick="requestColorMap('parula')">PARULA</button>
+        <button onclick="requestColorMap('HSV')">HSV</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- Footer -->
+  <footer>
+    <p>&copy; Digital Systems Project</p>
+  </footer>
+
+  <!-- For showing errors -->
+  <div class="modal-overlay" id="errorModal">
+    <div class="modal-content">
+      <h2>Error</h2>
+      <p id="errorMsg">Something went wrong</p>
+      <button id="errorOkBtn">OK</button>
+    </div>
+  </div>
+
+  <script>
+    // I keep references to our current file, image data, and various elements
+    let currentFile = null;
+    let originalPixels = null;
+    let mriValid = true; // Flag for whether the uploaded image is a valid MRI
+
+    const fileInput = document.getElementById('fileInput');
+    const analyzeBtn = document.getElementById('analyzeBtn');
+    const exportBtn = document.getElementById('exportBtn');
+
+    // The two main <img> elements
+    const mriImage = document.getElementById('mriImage');
+    const colorMapImage = document.getElementById('colorMapImage');
+
+    // Container for tumor analysis results
+    const analysisResultDiv = document.getElementById('analysisResult');
+
+    // Left canvas for annotations
+    const annotationCanvas = document.getElementById('annotationCanvas');
+    let annCtx = null;
+
+    // Right canvas for segmentation masks
+    const maskOverlay = document.getElementById('maskOverlay');
+    let maskCtx = null;
+
+    // Sliders for brightness and contrast
+    const brightnessRange = document.getElementById('brightnessRange');
+    const contrastRange = document.getElementById('contrastRange');
+
+    // Histogram canvas
+    const histogramCanvas = document.getElementById('histogramCanvas');
+    const histCtx = histogramCanvas.getContext('2d');
+
+    // Variables for drawing a selection rectangle on the left image
+    let drawing = false;
+    let startX = 0, startY = 0;
+    let currentX = 0, currentY = 0;
+
+    // Error references
+    const errorModal = document.getElementById('errorModal');
+    const errorMsg = document.getElementById('errorMsg');
+    const errorOkBtn = document.getElementById('errorOkBtn');
+
+    // Close the error box when "OK" is clicked
+    errorOkBtn.addEventListener('click', () => {
+      errorModal.style.display = 'none';
+    });
+
+    // Show any error message in the box
+    function showError(message) {
+      errorMsg.textContent = message;
+      errorModal.style.display = 'flex';
+    }
+
+    // Function to validate the uploaded image without processing segmentation
+    // This uses the /segment endpoint with a query parameter "validate=true"
+    async function validateMRI(file) {
+      const formData = new FormData();
+      formData.append('file', file);
+      try {
+        const response = await fetch('/segment?validate=true', {
+          method: 'POST',
+          body: formData
+        });
+        const data = await response.json();
+        if (!response.ok) {
+          if (data.error && data.error.toLowerCase().includes("mri")) {
+            mriValid = false;
+            showError("The uploaded image is not a valid MRI.");
+          } else {
+            mriValid = true;
+          }
+        } else {
+          mriValid = true;
+        }
+      } catch (err) {
+        console.error(err);
+        showError("Server error during MRI validation.");
+      }
+    }
+
+    // When a file is selected, read it in and display the original on the left,
+    // then immediately validate whether it's a valid MRI or not
+    fileInput.addEventListener('change', (e) => {
+      currentFile = e.target.files[0];
+      if (!currentFile) return;
+      if (!currentFile.type.match('image.*')) {
+        showError("Please select a valid image file (PNG/JPG).");
+        currentFile = null;
+        return;
+      }
+
+      const reader = new FileReader();
+      reader.onload = (evt) => {
+        // Put the image onto the left <img>
+        mriImage.src = evt.target.result;
+        mriImage.style.display = 'block';
+
+        // Clear out any old colormap image
+        colorMapImage.src = '';
+        colorMapImage.style.display = 'none';
+
+        // Reset the analysis result area
+        analysisResultDiv.style.display = 'none';
+        analysisResultDiv.innerHTML = '';
+      };
+      reader.readAsDataURL(currentFile);
+
+      // Immediately validate whether the uploaded image is a valid MRI or not
+      validateMRI(currentFile);
+    });
+
+    // Once the left image is loaded, we size up the annotation canvas
+    mriImage.onload = () => {
+      annotationCanvas.width = mriImage.clientWidth;
+      annotationCanvas.height = mriImage.clientHeight;
+      annCtx = annotationCanvas.getContext('2d');
+      annCtx.clearRect(0, 0, annotationCanvas.width, annotationCanvas.height);
+
+      // Reset brightness/contrast to defaults
+      brightnessRange.value = "0";
+      contrastRange.value = "100";
+
+      // I grab the raw pixels from the original image so I can manipulate them
+      loadOriginalPixels(mriImage).then((pixels) => {
+        autoRescale(pixels);
+        originalPixels = pixels;
+        drawHistogram(originalPixels);
+      });
+    };
+
+    // When a colormap image loads, this code displays it and preps the mask canvas
+    colorMapImage.onload = () => {
+      colorMapImage.style.display = 'block';
+      maskOverlay.width = colorMapImage.clientWidth;
+      maskOverlay.height = colorMapImage.clientHeight;
+      maskCtx = maskOverlay.getContext('2d');
+      maskCtx.clearRect(0, 0, maskOverlay.width, maskOverlay.height);
+    };
+
+    // Trigger the tumor analysis (classification)
+    analyzeBtn.addEventListener('click', async () => {
+      if (!currentFile) {
+        showError("Please select an MRI image before analysing.");
+        return;
+      }
+      if (!mriValid) {
+        showError("The uploaded image is not a valid MRI.");
+        return;
+      }
+      const formData = new FormData();
+      formData.append('file', currentFile);
+
+      try {
+        const response = await fetch('/analyze', {
+          method: 'POST',
+          body: formData
+        });
+        const data = await response.json();
+        if (!response.ok) {
+          showError(data.error || "Unknown error occurred.");
+          return;
+        }
+
+        // Extract the classification info and show it
+        const { tumor_type, probability } = data;
+        const confidence = (probability * 100).toFixed(2);
+        analysisResultDiv.style.display = 'block';
+        analysisResultDiv.innerHTML = `
+          <h3>Analysis Results</h3>
+          <p><strong>Tumor Type:</strong> ${tumor_type}</p>
+          <p><strong>Confidence:</strong> ${confidence}%</p>
+        `;
+      } catch (error) {
+        console.error(error);
+        showError("Server error during analysis.");
+      }
+    });
+
+    // Auto rescale function to stretch min/max intensities
+    function autoRescale(imageData) {
+      const data = imageData.data;
+      let minVal = 255;
+      let maxVal = 0;
+
+      for (let i = 0; i < data.length; i += 4) {
+        const val = data[i];
+        if (val < minVal) minVal = val;
+        if (val > maxVal) maxVal = val;
+      }
+      if (maxVal <= minVal) {
+        console.warn("No intensity variation found. Skipping rescale.");
+        return;
+      }
+      const range = maxVal - minVal;
+      for (let i = 0; i < data.length; i += 4) {
+        let r = data[i], g = data[i + 1], b = data[i + 2];
+        r = ((r - minVal) * 255) / range;
+        g = ((g - minVal) * 255) / range;
+        b = ((b - minVal) * 255) / range;
+        data[i]   = clamp(r, 0, 255);
+        data[i+1] = clamp(g, 0, 255);
+        data[i+2] = clamp(b, 0, 255);
+      }
+    }
+
+    // This adjusts brightness and contrast whenever sliders move
+    brightnessRange.addEventListener('input', applyBrightnessContrast);
+    contrastRange.addEventListener('input', applyBrightnessContrast);
+
+    function applyBrightnessContrast() {
+      if (!originalPixels) return;
+
+      const brightnessVal = parseInt(brightnessRange.value, 10);
+      const contrastVal = parseInt(contrastRange.value, 10);
+
+      const offCanvas = document.createElement('canvas');
+      offCanvas.width = originalPixels.width;
+      offCanvas.height = originalPixels.height;
+      const offCtx = offCanvas.getContext('2d');
+      offCtx.putImageData(originalPixels, 0, 0);
+
+      const imageData = offCtx.getImageData(0, 0, offCanvas.width, offCanvas.height);
+      const data = imageData.data;
+
+      // A formula for brightness and contrast
+      const contrastFactor = contrastVal / 100.0;
+      const brightnessOffset = brightnessVal * 2.55;
+
+      for (let i = 0; i < data.length; i += 4) {
+        let r = data[i] - 128;
+        let g = data[i + 1] - 128;
+        let b = data[i + 2] - 128;
+
+        r = r * contrastFactor + 128 + brightnessOffset;
+        g = g * contrastFactor + 128 + brightnessOffset;
+        b = b * contrastFactor + 128 + brightnessOffset;
+
+        data[i]   = clamp(r, 0, 255);
+        data[i+1] = clamp(g, 0, 255);
+        data[i+2] = clamp(b, 0, 255);
+      }
+      offCtx.putImageData(imageData, 0, 0);
+
+      colorMapImage.src = offCanvas.toDataURL('image/png');
+      colorMapImage.style.display = 'block';
+
+      // This clears any previously drawn mask
+      if (maskCtx) {
+        maskCtx.clearRect(0, 0, maskOverlay.width, maskOverlay.height);
+      }
+      drawHistogram(imageData);
+    }
+
+    // This clamps a value into a valid range of 0-255
+    function clamp(val, min, max) {
+      return Math.max(min, Math.min(max, val));
+    }
+
+    // This sets up mouse events to draw a rectangle on the left image
+    annotationCanvas.addEventListener('mousedown', (e) => {
+      drawing = true;
+      const rect = annotationCanvas.getBoundingClientRect();
+      startX = e.clientX - rect.left;
+      startY = e.clientY - rect.top;
+    });
+
+    annotationCanvas.addEventListener('mousemove', (e) => {
+      if (!drawing) return;
+      const rect = annotationCanvas.getBoundingClientRect();
+      currentX = e.clientX - rect.left;
+      currentY = e.clientY - rect.top;
+
+      annCtx.clearRect(0, 0, annotationCanvas.width, annotationCanvas.height);
+      annCtx.strokeStyle = 'lime';
+      annCtx.lineWidth = 2;
+      annCtx.strokeRect(startX, startY, currentX - startX, currentY - startY);
+    });
+
+    annotationCanvas.addEventListener('mouseup', () => {
+      drawing = false;
+    });
+
+    // If a user need to request a segmentation mask overlay
+    async function overlayMask() {
+      if (!currentFile) return;
+      const formData = new FormData();
+      formData.append('file', currentFile);
+
+      try {
+        const response = await fetch('/segment', { method: 'POST', body: formData });
+        const data = await response.json();
+        if (!response.ok) {
+          showError(data.error || "Segmentation failed.");
+          return;
+        }
+        const maskImg = new Image();
+        maskImg.onload = () => {
+          maskOverlay.width = colorMapImage.clientWidth;
+          maskOverlay.height = colorMapImage.clientHeight;
+          maskCtx.clearRect(0, 0, maskOverlay.width, maskOverlay.height);
+          maskCtx.globalAlpha = 0.4;
+          maskCtx.drawImage(maskImg, 0, 0, maskOverlay.width, maskOverlay.height);
+          maskCtx.globalAlpha = 1.0;
+        };
+        maskImg.src = "data:image/png;base64," + data.segmentation;
+      } catch (err) {
+        console.error(err);
+        showError("Server error during segmentation.");
+      }
+    }
+
+    // Sends the image and selected colormap to server, updates the right image
+    // Now before applying a color map, it checks whether the uploaded image is a valid MRI or not
+    async function requestColorMap(colormap) {
+      if (!currentFile) {
+        showError("Please select an MRI image before applying a color map.");
+        return;
+      }
+      if (!mriValid) {
+        showError("The uploaded image is not a valid MRI.");
+        return;
+      }
+      const formData = new FormData();
+      formData.append('file', currentFile);
+      formData.append('colormap', colormap);
+
+      try {
+        const response = await fetch('/colormap', {
+          method: 'POST',
+          body: formData
+        });
+        const data = await response.json();
+        if (!response.ok) {
+          showError(data.error || "Unknown error occurred.");
+          return;
+        }
+        colorMapImage.src = "data:image/png;base64," + data.colormapped;
+        colorMapImage.style.display = 'block';
+
+        // Clear mask if there is any
+        if (maskCtx) {
+          maskCtx.clearRect(0, 0, maskOverlay.width, maskOverlay.height);
+        }
+      } catch (err) {
+        console.error(err);
+        showError("Server error applying colormap.");
+      }
+    }
+
+    // Draw a histogram of the image intensities
+    function drawHistogram(imageDataOrPixels) {
+      const data = imageDataOrPixels.data;
+      const hist = new Array(256).fill(0);
+
+      // Counting frequencies for each gray level in red channel
+      for (let i = 0; i < data.length; i += 4) {
+        const val = data[i];
+        hist[val]++;
+      }
+
+      histCtx.clearRect(0, 0, histogramCanvas.width, histogramCanvas.height);
+      const maxCount = Math.max(...hist);
+      const barWidth = histogramCanvas.width / 256;
+
+      histCtx.fillStyle = "#fff";
+      for (let i = 0; i < 256; i++) {
+        const barHeight = (hist[i] / maxCount) * histogramCanvas.height;
+        histCtx.fillRect(i * barWidth, histogramCanvas.height - barHeight, barWidth, barHeight);
+      }
+    }
+
+    // Reads raw pixel data from an <img> by drawing it onto a canvas
+    async function loadOriginalPixels(img) {
+      const offCanvas = document.createElement('canvas');
+      offCanvas.width = img.naturalWidth;
+      offCanvas.height = img.naturalHeight;
+      const offCtx = offCanvas.getContext('2d');
+      offCtx.drawImage(img, 0, 0);
+      return offCtx.getImageData(0, 0, offCanvas.width, offCanvas.height);
+    }
+
+    // Code below exports the left image along with any annotation drawn on top
+    // Now checks if the uploaded image is a valid MRI before exporting
+    exportBtn.addEventListener('click', () => {
+      // If no image is selected, the user can't export anything
+      if (!currentFile || !mriImage.src) {
+        showError("No MRI image loaded to export.");
+        return;
+      }
+      if (!mriValid) {
+        showError("The uploaded image is not a valid MRI.");
+        return;
+      }
+      // Merge the MRI image and annotation canvas.
+      const comboCanvas = document.createElement('canvas');
+      comboCanvas.width = mriImage.width;
+      comboCanvas.height = mriImage.height;
+      const cCtx = comboCanvas.getContext('2d');
+
+      cCtx.drawImage(mriImage, 0, 0, comboCanvas.width, comboCanvas.height);
+      cCtx.drawImage(annotationCanvas, 0, 0, comboCanvas.width, comboCanvas.height);
+
+      // This code triggers the download
+      const link = document.createElement('a');
+      link.download = 'annotated_mri.png';
+      link.href = comboCanvas.toDataURL('image/png');
+      link.click();
+    });
+  </script>
+</body>
+</html>
\ No newline at end of file
-- 
GitLab