Spaces:
Sleeping
Sleeping
| {% extends "base.html" %} | |
| {% block title %}Upload Scan β AI Medical Intelligence Pipeline{% endblock %} | |
| {% block content %} | |
| <section class="breadcrumb"> | |
| <a href="{{ url_for('home') }}">Home</a> | |
| <span class="sep">/</span> | |
| <span>Upload Scans</span> | |
| </section> | |
| <section class="upload-hero"> | |
| <h1>Upload DICOM Scans</h1> | |
| <p> | |
| Upload one or many CT brain scans for AI-powered hemorrhage screening. | |
| A single exam may contain hundreds of slices β all modes below handle | |
| that seamlessly. | |
| </p> | |
| </section> | |
| {% with messages = get_flashed_messages(with_categories=true) %} | |
| {% if messages %} | |
| <div class="flash-messages"> | |
| {% for category, message in messages %} | |
| <div class="flash flash-{{ category }}">{{ message }}</div> | |
| {% endfor %} | |
| </div> | |
| {% endif %} | |
| {% endwith %} | |
| <!-- ββ Tab navigation ββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <div class="upload-tabs" role="tablist"> | |
| <button class="upload-tab active" data-tab="single" role="tab">Single File</button> | |
| <button class="upload-tab" data-tab="multi" role="tab">Multi-File / ZIP</button> | |
| {% if local_mode %} | |
| <button class="upload-tab" data-tab="dirscan" role="tab">Scan Directory</button> | |
| {% endif %} | |
| </div> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- TAB 1 β Single .dcm file --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <section class="panel upload-panel tab-panel active" id="tab-single"> | |
| <form method="post" action="{{ url_for('analyze') }}" enctype="multipart/form-data" id="singleForm"> | |
| <div class="dropzone" id="dropzoneSingle"> | |
| <svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" | |
| stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> | |
| <polyline points="17 8 12 3 7 8" /> | |
| <line x1="12" y1="3" x2="12" y2="15" /> | |
| </svg> | |
| <p class="dropzone-text">Drag & drop a .dcm file here</p> | |
| <p class="muted small">or click to browse</p> | |
| <input type="file" name="file" id="singleInput" accept=".dcm" hidden /> | |
| </div> | |
| <div class="file-info" id="singleInfo" style="display: none"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> | |
| <polyline points="14 2 14 8 20 8" /> | |
| </svg> | |
| <span id="singleFileName"></span> | |
| <button type="button" class="btn btn-sm btn-ghost js-clear-single">Remove</button> | |
| </div> | |
| <button type="submit" class="btn btn-primary" id="singleSubmit" disabled> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M22 12h-4l-3 9L9 3l-3 9H2" /> | |
| </svg> | |
| Analyze Scan | |
| </button> | |
| </form> | |
| <div class="loading-overlay" id="singleOverlay" style="display: none"> | |
| <div class="spinner"></div> | |
| <p>Running AI analysis…</p> | |
| <p class="muted small">This may take a moment on first run while the model loads.</p> | |
| </div> | |
| </section> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- TAB 2 β Multi-file / ZIP upload --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <section class="panel upload-panel tab-panel" id="tab-multi"> | |
| <form method="post" action="{{ url_for('analyze') }}" enctype="multipart/form-data" id="multiForm"> | |
| <div class="dropzone" id="dropzoneMulti"> | |
| <svg width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" | |
| stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="2" y="7" width="20" height="14" rx="2" /> | |
| <path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" /> | |
| </svg> | |
| <p class="dropzone-text">Drag & drop .dcm files or a .zip archive</p> | |
| <p class="muted small">Select multiple files, or a single .zip containing DICOM slices</p> | |
| <input type="file" name="file" id="multiInput" accept=".dcm,.zip" multiple hidden /> | |
| </div> | |
| <div class="file-info" id="multiInfo" style="display: none"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <rect x="2" y="7" width="20" height="14" rx="2" /> | |
| <path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" /> | |
| </svg> | |
| <span id="multiFileName"></span> | |
| <button type="button" class="btn btn-sm btn-ghost js-clear-multi">Remove all</button> | |
| </div> | |
| <button type="submit" class="btn btn-primary" id="multiSubmit" disabled> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M22 12h-4l-3 9L9 3l-3 9H2" /> | |
| </svg> | |
| Analyze Batch | |
| </button> | |
| </form> | |
| <div class="loading-overlay" id="multiOverlay" style="display: none"> | |
| <div class="spinner"></div> | |
| <p>Uploading files…</p> | |
| <p class="muted small">Large batches may take a moment to upload.</p> | |
| </div> | |
| </section> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <!-- TAB 3 β Directory scan (local mode only) --> | |
| <!-- ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| {% if local_mode %} | |
| <section class="panel upload-panel tab-panel" id="tab-dirscan"> | |
| <form method="post" action="{{ url_for('analyze_directory') }}" id="dirForm"> | |
| <label class="dir-label" for="dirPath"> | |
| Server-side directory containing .dcm files | |
| </label> | |
| <div class="dir-input-row"> | |
| <input type="text" name="dir_path" id="dirPath" class="input" placeholder="D:\scans\patient_001" | |
| spellcheck="false" autocomplete="off" /> | |
| <button type="submit" class="btn btn-primary" id="dirSubmit"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <circle cx="11" cy="11" r="8" /> | |
| <line x1="21" y1="21" x2="16.65" y2="16.65" /> | |
| </svg> | |
| Scan & Analyze | |
| </button> | |
| </div> | |
| <p class="muted small" style="margin-top: 8px"> | |
| The server will recursively find all <code>.dcm</code> files in this | |
| directory and its sub-folders, then run inference on each. | |
| This option is only available when running locally. | |
| </p> | |
| </form> | |
| </section> | |
| {% endif %} | |
| <!-- ββ How it works ββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <section class="panel" style="margin-top: 16px"> | |
| <h3>How It Works</h3> | |
| <div class="steps-grid"> | |
| <div class="step"> | |
| <div class="step-num">1</div> | |
| <div class="step-text"> | |
| <strong>Upload</strong> | |
| <p class="muted small">Select DICOM files, a ZIP, or enter a directory path</p> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">2</div> | |
| <div class="step-text"> | |
| <strong>Process</strong> | |
| <p class="muted small">CT windowing & preprocessing on each slice</p> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">3</div> | |
| <div class="step-text"> | |
| <strong>Analyze</strong> | |
| <p class="muted small">EfficientNet-B4 model with calibrated scoring</p> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-num">4</div> | |
| <div class="step-text"> | |
| <strong>Report</strong> | |
| <p class="muted small">Grad-CAM visualization & clinical report per slice</p> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| {% endblock %} | |
| {% block scripts %} | |
| <script src="{{ url_for('static', filename='js/upload.js') }}" defer></script> | |
| {% endblock %} |