Files
sinogram/public/CTAccordion .js

116 lines
3.7 KiB
JavaScript

export const steps = [
{
title: "1. Upload / Input Image",
content: `You upload or use a default grayscale image. This is treated like a 2D slice of a physical object.
The input image is a 2D function:
$ f(x, y) $
This represents how much X-rays are absorbed at each point.`,
},
{
title: "2. Radon Transform (Generating the Sinogram)",
content: `We rotate a virtual X-ray beam around the image and compute line integrals at each angle — simulating how X-rays pass through.
\\[
R(\\theta, s) = \\int_{-\\infty}^{\\infty} f(s \\cos\\theta - t \\sin\\theta,\\ s \\sin\\theta + t \\cos\\theta)\\ dt
\\]
Where: <br>
- $\\theta$ = projection angle <br>
- $s$ = offset from center
![Radon transform](https://upload.wikimedia.org/wikipedia/commons/9/93/Radon_transform_sinogram.gif)`,
},
{
title: "3. Sinogram",
content: `You now have a 2D image where:
- X-axis = detector position
- Y-axis = angle
Each row is a projection.
Math:
$\text{Sinogram} = \{ R(\theta_1, s), R(\theta_2, s), \dots, R(\theta_n, s) \}$
![Sinogram](https://www.researchgate.net/profile/Samuel-Asante-2/publication/299856137/figure/fig3/AS:348226420002817@1460035056245/A-Shepp-Logan-Phantom-and-reconstructed-Image-Sinogram-a-Original-image-b-radon.png)`,
},
{
title: "4. Optional: Apply Ramp Filter (FBP)",
content: `If enabled, we sharpen each projection before back-projecting by amplifying high-frequency content.
Math:
\\[
P(\\omega) = \\mathcal{F}[R(\\theta, s)] \\\\
P'(\\omega) = |\\omega| \\cdot P(\\omega) \\\\
\\text{Filtered Projection} = \\mathcal{F}^{-1}[P'(\\omega)]
\\]
![Ramp filter graph](https://www.researchgate.net/publication/346858231/figure/fig4/AS:967035467091970@1607570630558/Applying-Ramp-filter-to-a-sinogram-preserves-high-frequency-features-The-filter-is.png)`,
},
{
title: "5. Back Projection",
content: `Each (filtered) projection is \"smeared\" back into the image space along its angle.
\\[
f'(x, y) = \\int_0^{\\pi} R'(\\theta,\\ x \\cos\\theta + y \\sin\\theta)\\ d\\theta
\\]`,
},
{
title: "6. Final Reconstruction",
content: `After all angles are added up, you get a reconstructed image resembling the original.
\[
f'(x, y) \approx f(x, y)
\]`,
},
];
export const StepAccordion = {
view(vnode) {
const index = vnode.attrs.index;
const step = steps[index];
const expanded = vnode.state.expanded ?? false;
return m("div", { class: "border-b border-gray-300 py-1 my-2" }, [
m(
"button",
{
class:
"w-full text-left font-bold text-lg text-gray-800 focus:outline-none",
onclick: () => {
vnode.state.expanded = !vnode.state.expanded;
},
},
step.title
),
vnode.state.expanded &&
m(
"div",
{
class: "mt-2 text-gray-700 whitespace-pre-wrap text-sm",
onupdate: () => {
if (window.MathJax) window.MathJax.typesetPromise();
},
},
m.trust(markdownToHTML(step.content))
),
]);
},
};
// Use marked.js for robust Markdown to HTML conversion
const renderer = new marked.Renderer();
renderer.image = (href, title, text) => {
// Add Tailwind classes for styling images
return `<img src="${href}" alt="${text}" title="${
title || ""
}" class="my-2 rounded shadow max-w-full h-auto mx-auto">`;
};
marked.setOptions({ renderer });
function markdownToHTML(text) {
// Replace escaped newlines from template literals with actual newlines for marked
const processedText = text.replace(/\\n/g, "\n");
return marked.parse(processedText);
}