diff --git a/atlas/templates/atlas/help.html b/atlas/templates/atlas/help.html index eab9f111..ba40b47e 100644 --- a/atlas/templates/atlas/help.html +++ b/atlas/templates/atlas/help.html @@ -52,8 +52,9 @@
Step 3: Integrate the uploader with PACs
Whilst in PACs open the settings menu with the keyboard shortcut 'Ctrl+Shift+C'. In the 'General' tab, select 'External Tools' and click 'Add'. Press the Import button and select the xml file you downloaded in Step 2. Modify the file to run path to point the uploader exe downloaded in step 1 (clicking on the 3 dots will allow you to browse and select this file). The remaining fields should have been set via the xml file and should not require modification.

Uploading without the uploader tool

-

If you have annonymised dicom files already exported you can uploading them directly using this form

+

If you have annonymised dicom files already exported you can uploading them directly on this page using this page

To do so you will have to first export the files from the PACs system.

+

Once uploaded the series will be available to be imported into cases {% endblock %} diff --git a/atlas/templates/atlas/new_uploads.html b/atlas/templates/atlas/new_uploads.html index a31d6009..aab3bed3 100644 --- a/atlas/templates/atlas/new_uploads.html +++ b/atlas/templates/atlas/new_uploads.html @@ -11,14 +11,15 @@

-

This form allows you to quickly upload dicom files to the server. Select the folder containing the dicom files below. If you wish to upload files directly from Insight you may rather use the Uploader tool.

-
- Help +

This form allows you to quickly upload dicom files to the server. Select the folder containing the dicom files below. If you wish to upload files directly from Insight you may prefer to use the Uploader tool.

+
+ Help

Only valid dicom files will be uploaded.

It is possible to upload multiple files by selecting the parent (containing) folder. Folders within will then be searched recursively for any valid dicom files.

Files to be uploaded will be displayed below.

DICOM tags will be checked to see if valid accession or hospital numbers are present but no annonymisation will be performed.

-

By default a basic OCR will also be performed to check for burned in details on images, the is resource intensive and can be disabled if you know the content you are uploading does not feature any patient details.

+

An attempt to detect duplicates will be made by calculating a hash of the pixel data in the DICOM files. If this check is not run and the files are uploaded they will be rejected.

+

If enabled a basic OCR will also be performed to check for burned in details on images, this is resource intensive and can be disabled if you know the content you are uploading does not feature any patient details.

Settings @@ -97,28 +98,28 @@ window.upload_count = 1; window.duplicate_series = new Set() -async function calculateBlake3Hash(file) { + async function calculateBlake3Hash(file) { // Read file as ArrayBuffer - const buffer = await file.arrayBuffer(); + const buffer = await file.arrayBuffer(); // Parse as DICOM - let pixelDataElement; - try { - const byteArray = new Uint8Array(buffer); - const dataSet = dicomParser.parseDicom(byteArray); - pixelDataElement = dataSet.elements.x7fe00010; // Pixel Data tag - if (!pixelDataElement) { - throw new Error("No Pixel Data found in DICOM file."); - } + let pixelDataElement; + try { + const byteArray = new Uint8Array(buffer); + const dataSet = dicomParser.parseDicom(byteArray); + pixelDataElement = dataSet.elements.x7fe00010; // Pixel Data tag + if (!pixelDataElement) { + throw new Error("No Pixel Data found in DICOM file."); + } // Extract pixel data bytes - const pixelData = byteArray.subarray(pixelDataElement.dataOffset, pixelDataElement.dataOffset + pixelDataElement.length); + const pixelData = byteArray.subarray(pixelDataElement.dataOffset, pixelDataElement.dataOffset + pixelDataElement.length); // Hash just the pixel data - return await hashwasm.blake3(pixelData); - } catch (err) { - console.error("DICOM parse/hash error:", err, file.name); + return await hashwasm.blake3(pixelData); + } catch (err) { + console.error("DICOM parse/hash error:", err, file.name); // Fallback: hash the whole file if pixel data is missing or parsing fails - return await hashwasm.blake3(new Uint8Array(buffer)); - } -} + return await hashwasm.blake3(new Uint8Array(buffer)); + } + } async function checkForDuplicates(files) { const hashes = []; for (const file of files) { @@ -138,94 +139,94 @@ async function calculateBlake3Hash(file) { } -async function processFilesWithDuplicateCheck() { - const file_set = document.getElementById("filepicker").files; - let hasDuplicates = false; - const $dupProgress = $("#duplicate-progress"); - $dupProgress.empty(); + async function processFilesWithDuplicateCheck() { + const file_set = document.getElementById("filepicker").files; + let hasDuplicates = false; + const $dupProgress = $("#duplicate-progress"); + $dupProgress.empty(); - if ($("#check-duplicates").is(":checked")) { - $dupProgress.html('Calculating hashes...'); - $("#progress-text").text("Checking for duplicates..."); + if ($("#check-duplicates").is(":checked")) { + $dupProgress.html('Calculating hashes...'); + $("#progress-text").text("Checking for duplicates..."); // Calculate hashes and update progress - const hashes = []; - let n = 0; - for (const file of file_set) { - n += 1; - $dupProgress.html(`Hashing: ${file.webkitRelativePath} (${n} of ${file_set.length})`); - const hash = await calculateBlake3Hash(file); - hashes.push(hash); - file._blake3_hash = hash; - } + const hashes = []; + let n = 0; + for (const file of file_set) { + n += 1; + $dupProgress.html(`Hashing: ${file.webkitRelativePath} (${n} of ${file_set.length})`); + const hash = await calculateBlake3Hash(file); + hashes.push(hash); + file._blake3_hash = hash; + } - $dupProgress.html('Checking hashes with server...'); - const result = await fetch("{% url 'api-1:check_images_hashes' %}", { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRFToken": "{{ csrf_token }}" - }, - body: JSON.stringify(hashes) - }).then(res => res.json()); + $dupProgress.html('Checking hashes with server...'); + const result = await fetch("{% url 'api-1:check_images_hashes' %}", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": "{{ csrf_token }}" + }, + body: JSON.stringify(hashes) + }).then(res => res.json()); // Build a map from hash to file for quick lookup - const hashToFile = {}; - for (const file of file_set) { - if (file._blake3_hash) { - hashToFile[file._blake3_hash] = file; - } - } - - let duplicateCount = 0; - // Map of unique series url to {url, id} - const duplicateSeriesMap = {}; - for (const hash in result) { - if (result[hash].id) { - hasDuplicates = true; - duplicateCount += 1; - // Highlight the file in the listing - const file = hashToFile[hash]; - if (file) { - const selector = `.dicom-file[data-path='${file.webkitRelativePath}']`; - const li = document.querySelector(selector); - if (li) { - li.classList.add("duplicate-file-highlight"); + const hashToFile = {}; + for (const file of file_set) { + if (file._blake3_hash) { + hashToFile[file._blake3_hash] = file; } } - // Collect unique series by url - if (result[hash].url) { - duplicateSeriesMap[result[hash].url] = { - url: result[hash].url, - id: result[hash].id - }; - } - } - } - if (hasDuplicates) { - $("#uploadButton").prop("disabled", true); + let duplicateCount = 0; + // Map of unique series url to {url, id} + const duplicateSeriesMap = {}; + for (const hash in result) { + if (result[hash].id) { + hasDuplicates = true; + duplicateCount += 1; + // Highlight the file in the listing + const file = hashToFile[hash]; + if (file) { + const selector = `.dicom-file[data-path='${file.webkitRelativePath}']`; + const li = document.querySelector(selector); + if (li) { + li.classList.add("duplicate-file-highlight"); + } + } + // Collect unique series by url + if (result[hash].url) { + duplicateSeriesMap[result[hash].url] = { + url: result[hash].url, + id: result[hash].id + }; + } + } + } + + if (hasDuplicates) { + $("#uploadButton").prop("disabled", true); // Show summary in red and list duplicate series as links if any - let html = `
Found ${duplicateCount} duplicate file(s). Please review before uploading.
`; - const seriesUrls = Object.values(duplicateSeriesMap); - if (seriesUrls.length > 0) { - html += `
Duplicate series:
`; + let html = `
Found ${duplicateCount} duplicate file(s). Please review before uploading.
`; + const seriesUrls = Object.values(duplicateSeriesMap); + if (seriesUrls.length > 0) { + html += `
Duplicate series:
`; + } + $dupProgress.html(html); + toastr.error("Duplicates detected. Please review before uploading."); + } else { + $("#uploadButton").prop("disabled", false); + $dupProgress.html('No duplicates detected.'); + } + } else { + $("#uploadButton").prop("disabled", false); + $dupProgress.empty(); } - $dupProgress.html(html); - toastr.error("Duplicates detected. Please review before uploading."); - } else { - $("#uploadButton").prop("disabled", false); - $dupProgress.html('No duplicates detected.'); } - } else { - $("#uploadButton").prop("disabled", false); - $dupProgress.empty(); - } -} function* chunks(arr, n) { @@ -631,11 +632,11 @@ async function processFilesWithDuplicateCheck() { {% endblock %} {% block css %} - + {% endblock %} \ No newline at end of file