Make generated singogram easier to see
This commit is contained in:
@@ -230,6 +230,7 @@ export const UploadImageComponent = {
|
|||||||
src: this.sinogramUrl,
|
src: this.sinogramUrl,
|
||||||
alt: "Sinogram",
|
alt: "Sinogram",
|
||||||
class: "rounded shadow max-w-full h-auto mx-auto",
|
class: "rounded shadow max-w-full h-auto mx-auto",
|
||||||
|
style: "width: 100%; max-height: 300px;",
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
|||||||
@@ -18,51 +18,49 @@ export async function generateSinogram(
|
|||||||
for (let angle = 0; angle < angles; angle++) {
|
for (let angle = 0; angle < angles; angle++) {
|
||||||
const theta = (angle * Math.PI) / angles;
|
const theta = (angle * Math.PI) / angles;
|
||||||
|
|
||||||
// 🔁 Call visual overlay for this angle
|
|
||||||
if (drawAngleCallback) drawAngleCallback(theta);
|
if (drawAngleCallback) drawAngleCallback(theta);
|
||||||
|
|
||||||
// (Optional: add delay for animation)
|
|
||||||
await new Promise((r) => setTimeout(r, 0.01));
|
await new Promise((r) => setTimeout(r, 0.01));
|
||||||
|
|
||||||
// Clear canvas
|
|
||||||
ctx.clearRect(0, 0, size, size);
|
ctx.clearRect(0, 0, size, size);
|
||||||
|
|
||||||
// Transform and draw rotated image
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(size / 2, size / 2);
|
ctx.translate(size / 2, size / 2);
|
||||||
ctx.rotate(theta);
|
ctx.rotate(theta);
|
||||||
ctx.drawImage(image, -image.width / 2, -image.height / 2);
|
ctx.drawImage(image, -image.width / 2, -image.height / 2);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
// Read pixel data
|
|
||||||
const { data } = ctx.getImageData(0, 0, size, size);
|
const { data } = ctx.getImageData(0, 0, size, size);
|
||||||
|
|
||||||
// Sum brightness vertically (simulate X-ray projection)
|
|
||||||
const projection = [];
|
const projection = [];
|
||||||
for (let x = 0; x < size; x++) {
|
for (let x = 0; x < size; x++) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for (let y = 0; y < size; y++) {
|
for (let y = 0; y < size; y++) {
|
||||||
const i = (y * size + x) * 4;
|
const i = (y * size + x) * 4;
|
||||||
const gray = data[i]; // red channel (since grayscale)
|
sum += data[i]; // grayscale from red channel
|
||||||
sum += gray;
|
|
||||||
}
|
}
|
||||||
projection.push(sum / size); // normalize
|
projection.push(sum / size);
|
||||||
}
|
}
|
||||||
projections.push(projection);
|
projections.push(projection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create sinogram canvas
|
const isHorizontal = angles >= size;
|
||||||
|
|
||||||
|
// Create rotated canvas accordingly
|
||||||
const sinogramCanvas = Object.assign(document.createElement("canvas"), {
|
const sinogramCanvas = Object.assign(document.createElement("canvas"), {
|
||||||
width: size,
|
width: isHorizontal ? size : angles,
|
||||||
height: angles,
|
height: isHorizontal ? angles : size,
|
||||||
});
|
});
|
||||||
const sinCtx = sinogramCanvas.getContext("2d");
|
const sinCtx = sinogramCanvas.getContext("2d");
|
||||||
const imgData = sinCtx.createImageData(size, angles);
|
const imgData = sinCtx.createImageData(
|
||||||
|
sinogramCanvas.width,
|
||||||
|
sinogramCanvas.height
|
||||||
|
);
|
||||||
|
|
||||||
for (let y = 0; y < angles; y++) {
|
for (let angle = 0; angle < angles; angle++) {
|
||||||
for (let x = 0; x < size; x++) {
|
for (let x = 0; x < size; x++) {
|
||||||
const val = projections[y][x];
|
const val = projections[angle][x];
|
||||||
const i = (y * size + x) * 4;
|
const px = isHorizontal ? x : angle;
|
||||||
|
const py = isHorizontal ? angle : x;
|
||||||
|
const i = (py * sinogramCanvas.width + px) * 4;
|
||||||
imgData.data[i + 0] = val;
|
imgData.data[i + 0] = val;
|
||||||
imgData.data[i + 1] = val;
|
imgData.data[i + 1] = val;
|
||||||
imgData.data[i + 2] = val;
|
imgData.data[i + 2] = val;
|
||||||
@@ -95,7 +93,18 @@ export async function reconstructImageFromSinogram(
|
|||||||
sinogramImage.height
|
sinogramImage.height
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
size = sinogramImage.width; // match size to sinogram resolution
|
// Detect orientation
|
||||||
|
let width, angles;
|
||||||
|
const isVertical = sinogramImage.height > sinogramImage.width;
|
||||||
|
if (isVertical) {
|
||||||
|
angles = sinogramImage.width;
|
||||||
|
width = sinogramImage.height;
|
||||||
|
} else {
|
||||||
|
angles = sinogramImage.height;
|
||||||
|
width = sinogramImage.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = width;
|
||||||
const outputCanvas = Object.assign(document.createElement("canvas"), {
|
const outputCanvas = Object.assign(document.createElement("canvas"), {
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
@@ -104,17 +113,18 @@ export async function reconstructImageFromSinogram(
|
|||||||
const accum = new Float32Array(size * size);
|
const accum = new Float32Array(size * size);
|
||||||
const center = size / 2;
|
const center = size / 2;
|
||||||
|
|
||||||
const angles = sinogramImage.height;
|
|
||||||
const width = sinogramImage.width;
|
|
||||||
|
|
||||||
for (let angle = 0; angle < angles; angle++) {
|
for (let angle = 0; angle < angles; angle++) {
|
||||||
const theta = (angle * Math.PI) / angles;
|
const theta = (angle * Math.PI) / angles;
|
||||||
|
|
||||||
let projection = [];
|
let projection = [];
|
||||||
for (let x = 0; x < width; x++) {
|
for (let x = 0; x < width; x++) {
|
||||||
const i = (angle * width + x) * 4;
|
const i = isVertical
|
||||||
|
? (x * angles + angle) * 4 // transposed layout
|
||||||
|
: (angle * width + x) * 4; // normal layout
|
||||||
|
|
||||||
projection.push(sinogramData[i]);
|
projection.push(sinogramData[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useFBP) {
|
if (useFBP) {
|
||||||
projection = applyRampFilter(projection);
|
projection = applyRampFilter(projection);
|
||||||
}
|
}
|
||||||
@@ -122,7 +132,7 @@ export async function reconstructImageFromSinogram(
|
|||||||
for (let y = 0; y < size; y++) {
|
for (let y = 0; y < size; y++) {
|
||||||
for (let x = 0; x < size; x++) {
|
for (let x = 0; x < size; x++) {
|
||||||
const x0 = x - center;
|
const x0 = x - center;
|
||||||
const y0 = center - y; // flip y
|
const y0 = center - y;
|
||||||
const s = Math.round(
|
const s = Math.round(
|
||||||
x0 * Math.cos(theta) + y0 * Math.sin(theta) + width / 2
|
x0 * Math.cos(theta) + y0 * Math.sin(theta) + width / 2
|
||||||
);
|
);
|
||||||
@@ -133,7 +143,6 @@ export async function reconstructImageFromSinogram(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (onFrame) {
|
if (onFrame) {
|
||||||
// normalize and draw current frame
|
|
||||||
let maxVal = 0;
|
let maxVal = 0;
|
||||||
for (let i = 0; i < accum.length; i++) {
|
for (let i = 0; i < accum.length; i++) {
|
||||||
if (accum[i] > maxVal) maxVal = accum[i];
|
if (accum[i] > maxVal) maxVal = accum[i];
|
||||||
@@ -154,6 +163,7 @@ export async function reconstructImageFromSinogram(
|
|||||||
imageData.data[i * 4 + 2] = b;
|
imageData.data[i * 4 + 2] = b;
|
||||||
imageData.data[i * 4 + 3] = 255;
|
imageData.data[i * 4 + 3] = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputCtx.putImageData(imageData, 0, 0);
|
outputCtx.putImageData(imageData, 0, 0);
|
||||||
await new Promise((r) => setTimeout(r, 1));
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
onFrame(angle, outputCanvas.toDataURL());
|
onFrame(angle, outputCanvas.toDataURL());
|
||||||
|
|||||||
Reference in New Issue
Block a user