Add raw and filtered projection graphs
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
} from "./sinogram.js";
|
||||
|
||||
import { StepAccordion } from "./CTAccordion .js";
|
||||
import { ProjectionGraphComponent } from "./ProjectionGraphComponent.js";
|
||||
|
||||
export const UploadImageComponent = {
|
||||
hasLoadedInitialImage: false,
|
||||
@@ -21,6 +22,10 @@ export const UploadImageComponent = {
|
||||
manualAngleIndex: 0, // For the new angle control slider
|
||||
sinogramImageElement: null, // To store the sinogram img DOM element
|
||||
sinogramHighlightCanvas: null, // Canvas for sinogram highlight
|
||||
rawProjections: [], // To store raw projections from generateSinogram
|
||||
filteredProjections: [], // To store filtered projections from reconstructImageFromSinogram
|
||||
currentRawProjectionData: null, // Data for the raw projection graph
|
||||
currentFilteredProjectionData: null, // Data for the filtered projection graph
|
||||
renderMode: "grayscale", // or "heatmap"
|
||||
filterType: "ramp", // Added to manage selected filter
|
||||
|
||||
@@ -134,6 +139,10 @@ export const UploadImageComponent = {
|
||||
this.filteredSinogramUrl = null;
|
||||
this.reconstructedUrl = "loading"; // Indicate reconstruction is also loading
|
||||
this.manualAngleIndex = 0;
|
||||
this.rawProjections = [];
|
||||
this.filteredProjections = [];
|
||||
this.currentRawProjectionData = null;
|
||||
this.currentFilteredProjectionData = null;
|
||||
m.redraw();
|
||||
|
||||
let finalUrl = url;
|
||||
@@ -151,11 +160,20 @@ export const UploadImageComponent = {
|
||||
if (img) this.imageElement = img;
|
||||
}
|
||||
|
||||
this.sinogramUrl = await generateSinogram(
|
||||
const sinogramResult = await generateSinogram(
|
||||
finalUrl,
|
||||
this.angleCount,
|
||||
this.drawAngleOverlay.bind(this)
|
||||
);
|
||||
this.sinogramUrl = sinogramResult.sinogramUrl;
|
||||
this.rawProjections = sinogramResult.projections; // Store raw projections
|
||||
if (
|
||||
this.rawProjections &&
|
||||
this.rawProjections.length > this.manualAngleIndex
|
||||
) {
|
||||
this.currentRawProjectionData =
|
||||
this.rawProjections[this.manualAngleIndex];
|
||||
}
|
||||
m.redraw();
|
||||
|
||||
this.reconstructionFrames = [];
|
||||
@@ -181,6 +199,20 @@ export const UploadImageComponent = {
|
||||
|
||||
this.reconstructedUrl = reconstructionResult.reconstructedUrl;
|
||||
this.filteredSinogramUrl = reconstructionResult.filteredSinogramUrl;
|
||||
// Assuming reconstructImageFromSinogram now returns allFilteredProjections
|
||||
this.filteredProjections =
|
||||
reconstructionResult.allFilteredProjections || [];
|
||||
if (
|
||||
this.filteredProjections &&
|
||||
this.filteredProjections.length > this.manualAngleIndex
|
||||
) {
|
||||
this.currentFilteredProjectionData =
|
||||
this.filteredProjections[this.manualAngleIndex];
|
||||
}
|
||||
// It seems rawProjectionsFromSinogram is also returned, let's decide which raw projections to use.
|
||||
// For now, sticking with the ones from generateSinogram for the "raw projection" graph.
|
||||
// If `reconstructionResult.rawProjectionsFromSinogram` is preferred, adjust here.
|
||||
|
||||
m.redraw();
|
||||
},
|
||||
|
||||
@@ -473,9 +505,87 @@ export const UploadImageComponent = {
|
||||
(this.manualAngleIndex * Math.PI) / (this.angleCount || 1);
|
||||
this.drawAngleOverlay(theta);
|
||||
this.drawSinogramHighlight(this.manualAngleIndex);
|
||||
|
||||
// Update current projection data for graphs
|
||||
if (
|
||||
this.rawProjections &&
|
||||
this.rawProjections.length > this.manualAngleIndex
|
||||
) {
|
||||
this.currentRawProjectionData =
|
||||
this.rawProjections[this.manualAngleIndex];
|
||||
} else {
|
||||
this.currentRawProjectionData = null;
|
||||
}
|
||||
if (
|
||||
this.filteredProjections &&
|
||||
this.filteredProjections.length > this.manualAngleIndex
|
||||
) {
|
||||
this.currentFilteredProjectionData =
|
||||
this.filteredProjections[this.manualAngleIndex];
|
||||
} else {
|
||||
this.currentFilteredProjectionData = null;
|
||||
}
|
||||
// Redraw will be handled by Mithril automatically if currentRawProjectionData/currentFilteredProjectionData are used in view
|
||||
},
|
||||
}),
|
||||
]),
|
||||
// Projection Graphs
|
||||
(this.currentRawProjectionData ||
|
||||
this.currentFilteredProjectionData) &&
|
||||
m("div.mt-6.flex.flex-col.space-y-4", [
|
||||
this.currentRawProjectionData &&
|
||||
m(ProjectionGraphComponent, {
|
||||
title: "Current Raw Projection",
|
||||
data: this.currentRawProjectionData,
|
||||
width: 450, // Adjusted width
|
||||
height: 200, // Adjusted height
|
||||
color: "dodgerblue",
|
||||
}),
|
||||
this.currentFilteredProjectionData &&
|
||||
m(ProjectionGraphComponent, {
|
||||
title: "Current Filtered Projection",
|
||||
data: this.currentFilteredProjectionData,
|
||||
width: 450, // Adjusted width
|
||||
height: 200, // Adjusted height
|
||||
color: "orangered",
|
||||
}),
|
||||
]),
|
||||
// Filter Type Selector
|
||||
m("div", { class: "mt-6" }, [
|
||||
// Using mt-6 for spacing
|
||||
m(
|
||||
"label",
|
||||
{
|
||||
for: "filterType",
|
||||
class: "block text-sm font-medium text-gray-700 mb-1",
|
||||
},
|
||||
"Filter Type:"
|
||||
),
|
||||
m(
|
||||
"select",
|
||||
{
|
||||
id: "filterType",
|
||||
class:
|
||||
"mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md",
|
||||
value: this.filterType,
|
||||
onchange: (e) => {
|
||||
this.filterType = e.target.value;
|
||||
// Reload and reprocess when filter changes
|
||||
this.loadAndProcessDebounced(
|
||||
this.imageUrl,
|
||||
this.imageUrl !== this.defaultImageUrl
|
||||
);
|
||||
},
|
||||
},
|
||||
[
|
||||
m("option", { value: "none" }, "None"),
|
||||
m("option", { value: "ramp" }, "Ramp (Shepp-Logan)"),
|
||||
m("option", { value: "cosine" }, "Cosine"),
|
||||
m("option", { value: "hamming" }, "Hamming"),
|
||||
m("option", { value: "hann" }, "Hann"),
|
||||
]
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
|
||||
@@ -665,7 +775,7 @@ export const UploadImageComponent = {
|
||||
m(
|
||||
"div",
|
||||
{
|
||||
class: "mt-6 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4",
|
||||
class: "mt-6",
|
||||
},
|
||||
[
|
||||
m("div", [
|
||||
@@ -698,40 +808,6 @@ export const UploadImageComponent = {
|
||||
]
|
||||
),
|
||||
]),
|
||||
m("div", [
|
||||
m(
|
||||
"label",
|
||||
{
|
||||
for: "filterTypeSelect",
|
||||
class: "block text-sm font-medium text-gray-700 mb-1",
|
||||
},
|
||||
"Filter Type (Reconstruction):"
|
||||
),
|
||||
m(
|
||||
"select",
|
||||
{
|
||||
id: "filterTypeSelect",
|
||||
class:
|
||||
"mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md shadow-sm",
|
||||
value: this.filterType,
|
||||
onchange: (e) => {
|
||||
this.filterType = e.target.value;
|
||||
this.loadAndProcessDebounced(
|
||||
this.imageUrl,
|
||||
this.imageUrl !== this.defaultImageUrl
|
||||
);
|
||||
},
|
||||
},
|
||||
[
|
||||
m("option", { value: "ramp" }, "Ramp (Ram-Lak)"),
|
||||
m("option", { value: "shepp-logan" }, "Shepp-Logan"),
|
||||
m("option", { value: "cosine" }, "Cosine"),
|
||||
m("option", { value: "hamming" }, "Hamming"),
|
||||
m("option", { value: "hann" }, "Hann"),
|
||||
m("option", { value: "none" }, "None (Unfiltered)"),
|
||||
]
|
||||
),
|
||||
]),
|
||||
]
|
||||
),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user