Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions webct/blueprints/app/static/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,14 @@ function setPageLoading(loading: boolean, type: LoadingType = "default", source:
button.setAttribute("loading", "true");
}

// special buttons that also update the page, but are not dedicated to that feature.
// e.g quick-fire sample rotate buttons
let miscButtons = document.getElementsByClassName("button-loadonupdate");
for (let index = 0; index < miscButtons.length; index++) {
const button = miscButtons[index];
button.setAttribute("loading", "true")
}

document.getElementsByTagName("body")[0].style.cursor = "wait";

LoadingBar.setAttribute("variant", type);
Expand Down Expand Up @@ -259,6 +267,14 @@ function setPageLoading(loading: boolean, type: LoadingType = "default", source:
button.removeAttribute("loading");
}

// special buttons that also update the page, but are not dedicated to that feature.
// e.g quick-fire sample rotate buttons
let miscButtons = document.getElementsByClassName("button-loadonupdate");
for (let index = 0; index < miscButtons.length; index++) {
const button = miscButtons[index];
button.removeAttribute("loading");
}

document.getElementsByTagName("body")[0].style.cursor = "";
LoadingBar.setAttribute("hidden", "true");
}
Expand Down
25 changes: 24 additions & 1 deletion webct/blueprints/capture/static/js/capture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
* @author Iwan Mitchell
*/

import { SlDropdown, SlInput, SlRange, SlSelect } from "@shoelace-style/shoelace";
import { SlButton, SlDropdown, SlInput, SlRange, SlSelect } from "@shoelace-style/shoelace";
import { AlertType, showAlert } from "../../../base/static/js/base";
import { PanePixelSizeElement, PaneWidthElement } from "../../../detector/static/js/detector";
import { CaptureResponseRegistry, processResponse, requestCaptureData, sendCaptureData, prepareRequest, requestCapturePreview } from "./api";
import { CaptureConfigError, CaptureRequestError, showError } from "./errors";
import { CapturePreview, CaptureProperties } from "./types";
import { validateProjections } from "./validation";
import { UpdatePage } from "../../../app/static/js/app";

// ====================================================== //
// ================== Document Elements ================= //
Expand All @@ -26,6 +27,8 @@ let DetectorPosZElement: SlInput;
let SampleRotateXElement: SlInput;
let SampleRotateYElement: SlInput;
let SampleRotateZElement: SlInput;
let ButtonRotateClock45Element: SlButton;
let ButtonRotateCounterClock45Element: SlButton;

let PreviewImages: NodeListOf<HTMLImageElement>;
let PreviewOverlays: NodeListOf<HTMLDivElement>;
Expand Down Expand Up @@ -65,6 +68,9 @@ export function setupCapture(): boolean {
const sample_rotatey_element = document.getElementById("inputSampleRotateY");
const sample_rotatez_element = document.getElementById("inputSampleRotateZ");

const sample_rotate_counter_clock_45_element = document.getElementById("buttonSampleRotateCounterClock45");
const sample_rotate_clock_45_element = document.getElementById("buttonSampleRotateClock45");

const range_nyquist = document.getElementById("rangeNyquist");

if (total_rotation_element == null ||
Expand All @@ -75,6 +81,8 @@ export function setupCapture(): boolean {
sample_rotatex_element == null ||
sample_rotatey_element == null ||
sample_rotatez_element == null ||
sample_rotate_counter_clock_45_element == null ||
sample_rotate_clock_45_element == null ||
detector_posx_element == null ||
detector_posy_element == null ||
detector_posz_element == null ||
Expand All @@ -94,6 +102,8 @@ export function setupCapture(): boolean {
console.log(sample_rotatex_element);
console.log(sample_rotatey_element);
console.log(sample_rotatez_element);
console.log(sample_rotate_clock_45_element);
console.log(sample_rotate_counter_clock_45_element);
console.log(range_nyquist);

showAlert("Capture setup failure", AlertType.ERROR);
Expand All @@ -116,6 +126,8 @@ export function setupCapture(): boolean {
SampleRotateXElement = sample_rotatex_element as SlInput;
SampleRotateYElement = sample_rotatey_element as SlInput;
SampleRotateZElement = sample_rotatez_element as SlInput;
ButtonRotateClock45Element = sample_rotate_clock_45_element as SlButton;
ButtonRotateCounterClock45Element = sample_rotate_counter_clock_45_element as SlButton;

// Workaround hack to deal with styling annoying shadowroot classes
Array.prototype.slice.call(document.getElementsByTagName("sl-dropdown")).forEach((dropdown: SlDropdown) => {
Expand Down Expand Up @@ -152,6 +164,17 @@ export function setupCapture(): boolean {
});
}

ButtonRotateClock45Element.onclick = () => {
SampleRotateZElement.value = (parseFloat(SampleRotateZElement.value) + 45) % 360 + "";
UpdatePage();
};
ButtonRotateCounterClock45Element.onclick = () => {
let rot = (parseFloat(SampleRotateZElement.value) - 45)
if (rot < 0) { rot += 360 }
SampleRotateZElement.value = rot + "";
UpdatePage();
};

validateCapture();
SetOverlaySize(300, 300);
return true;
Expand Down
4 changes: 4 additions & 0 deletions webct/blueprints/capture/templates/tab.capture.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
<sl-input label="Yaw" type="number" min="0" max="360" step="0.1" id="inputSampleRotateY"></sl-input>
<sl-input label="Roll" type="number" min="0" max="360" step="0.1" id="inputSampleRotateZ"></sl-input>
</div>
<sl-button-group label="Quick Rotate">
<sl-button class="button-loadonupdate" id="buttonSampleRotateCounterClock45"><sl-icon slot="prefix" name="arrow-counterclockwise"></sl-icon>-45°</sl-button>
<sl-button class="button-loadonupdate" id="buttonSampleRotateClock45"><sl-icon slot="suffix" name="arrow-clockwise"></sl-icon>+45°</sl-button>
</sl-button-group>
</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion webct/blueprints/detector/static/js/detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AlertType, showAlert } from "../../../base/static/js/base";
import { SetPreviewSize } from "../../../preview/static/js/sim/projection";
import { DetectorResponseRegistry, prepareRequest, processResponse, requestDetectorData, sendDetectorData } from "./api";
import { DetectorConfigError, DetectorRequestError, showError } from "./errors";
import { DetectorProperties, EnergyResponseData, EnergyResponseDisplay, LSF, LSFDisplay, LSFParseEnum, ScintillatorMaterial } from "./types";
import { DetectorProperties, EnergyResponseDisplay, LSF, LSFDisplay, LSFParseEnum, ScintillatorMaterial } from "./types";
import { validateHeight, validatePixel, validateWidth } from "./validation";

// ====================================================== //
Expand Down
37 changes: 36 additions & 1 deletion webct/blueprints/preview/routes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from base64 import b64encode
from datetime import datetime
import io
from typing import List
import logging as log

Expand All @@ -24,15 +26,41 @@ def saveGif(array: np.ndarray) -> None:

images[0].save("projections.gif", "GIF", append_images=images[1:], duration=10, loop=0)

def getHistImage(array:np.ndarray, bins:List[float]) -> str:
# create a mask of pixels < bin[5]
# 0 - 1 - 2 - 3 - 4 - 5 - 6
mask = array < bins[5]

# create rgb image
array = (array - array.min()) / (array.max() - array.min())
array = (array * 255).astype("uint8")
array = np.stack([array, array, array], axis=2)
# set red channel of mask to 255, other channels to 0
array[mask, 0] = 255
array[mask, 1] = 0
array[mask, 2] = 0

# create png and base64 via bytestream
byteStream = io.BytesIO()
img = Image.fromarray(array, mode="RGB")
img.save(byteStream, "PNG")
byteStream.seek(0)
return str(b64encode(byteStream.read()))[2:-1]

@bp.route("/sim/preview/get")
def getPreviews() -> Response:
then = datetime.now()
sim = Sim(session)

projection = sim.projection(Quality.MEDIUM)
log_projection = np.log(projection)

hist, bins = sim.transmission_histogram()
histimgstr = getHistImage(projection, bins)

log.info(f"[{sim._sid}] Encoding projection preview")
projectionstr = asPngStr(projection)
log_projectionstr = asPngStr(log_projection)

layout = sim.layout()
log.info(f"[{sim._sid}] Encoding layout preview")
Expand All @@ -46,9 +74,16 @@ def getPreviews() -> Response:
{
"time": f"{(then-datetime.now()).total_seconds()}",
"projection": {
"image": projectionstr,
"image": {
"raw": projectionstr,
"log": log_projectionstr,
},
"height": projection.shape[0],
"width": projection.shape[1],
"transmission": {
"hist": hist,
"image": histimgstr,
}
},
"layout": {
"image": layoutstr,
Expand Down
20 changes: 17 additions & 3 deletions webct/blueprints/preview/static/js/sim/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ export interface SimResponseRegistry {
simResponse: {
time:number,
projection: {
image:string,
image: {
raw:string,
log:string,
},
height:number,
width:number
width:number,
transmission: {
hist:number[],
image:string,
}
},
layout: {
image:string,
Expand Down Expand Up @@ -74,9 +81,16 @@ export function processResponse(data: SimResponseRegistry["simResponse"]): Previ
const preview: PreviewData = {
time: data.time,
projection: {
image: data.projection.image,
image: {
raw: data.projection.image.raw,
log: data.projection.image.log
},
height: data.projection.height,
width: data.projection.width,
transmission: {
hist: data.projection.transmission.hist,
image: data.projection.transmission.image
},
},
layout: {
image: data.layout.image,
Expand Down
47 changes: 32 additions & 15 deletions webct/blueprints/preview/static/js/sim/projection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SlButton, SlCheckbox, SlRadio } from "@shoelace-style/shoelace";
import { processResponse, requestProjection, SimResponseRegistry } from "./api";
import { ProjectionRequestError, showError } from "./errors";
import { PreviewData } from "./types";
import { PreviewData, TransmissionDisplay } from "./types";
// import type { Buffer } from "buffer";

// ====================================================== //
Expand All @@ -11,11 +11,14 @@ import { PreviewData } from "./types";
let ButtonPreviewProjection: SlButton;
let ButtonPreviewLayout: SlButton;
let PreviewPane: HTMLDivElement;
let TransmissionCanvas: HTMLCanvasElement;

let SettingRawElement: SlRadio;
let SettingLogElement: SlRadio;
let SettingTransmissionElement:SlRadio;
let SettingInvertElement: SlCheckbox;


// ====================================================== //
// ====================================================== //
// ====================================================== //
Expand All @@ -27,6 +30,8 @@ let SceneImages: NodeListOf<HTMLImageElement>;
let PreviewData: PreviewData;
let PreviewPaneImage: HTMLImageElement;

let TransmissionChart: TransmissionDisplay;

export function MarkLoading(): void {
for (let index = 0; index < PreviewImages.length; index++) {
const image = PreviewImages[index];
Expand Down Expand Up @@ -61,6 +66,7 @@ export function updateProjection(): Promise<void> {

result.then((result: unknown) => {
PreviewData = processResponse(result as SimResponseRegistry["simResponse"]);
console.log(PreviewData);
updateImageDisplay();

for (let index = 0; index < PreviewImages.length; index++) {
Expand All @@ -81,29 +87,30 @@ export function updateProjection(): Promise<void> {
}

function updateImageDisplay(): void {
TransmissionChart = new TransmissionDisplay(PreviewData, TransmissionCanvas)
TransmissionChart.displayTransmission();

for (let index = 0; index < PreviewImages.length; index++) {
const image = PreviewImages[index];
image.src = "data:image/png;base64," + PreviewData.projection.image;
// image.src = "data:image/png;base64," + PreviewData.projection.image;

if (SettingInvertElement.checked) {
window.dispatchEvent(new CustomEvent("invertOn",{bubbles:true, cancelable:false}));
} else {
window.dispatchEvent(new CustomEvent("invertOff",{bubbles:true, cancelable:false}));
}

// if (!SettingInvertElement.checked) {
// if (SettingLogElement.checked) {
// image.style.backgroundImage = "url('" + "data:image/png;base64," + Images.imagelog.substring(2, Images.imagelog.length - 1) + "')";
// } else {
// image.style.backgroundImage = "url('" + "data:image/png;base64," + Images.image.substring(2, Images.image.length - 1) + "')";
// }
// } else {
// if (SettingLogElement.checked) {
// image.style.backgroundImage = "url('" + "data:image/png;base64," + Images.imageloginv.substring(2, Images.imageloginv.length - 1) + "')";
// } else {
// image.style.backgroundImage = "url('" + "data:image/png;base64," + Images.imageinv.substring(2, Images.imageinv.length - 1) + "')";
// }
// }
if (SettingRawElement.checked) {
image.src = "data:image/png;base64," + PreviewData.projection.image.raw;
} else if (SettingLogElement.checked) {
image.src = "data:image/png;base64," + PreviewData.projection.image.log;
}

if (SettingTransmissionElement.checked) {
image.src = "data:image/png;base64," + PreviewData.projection.transmission.image;
} else if (SettingTransmissionElement.checked) {
image.src = "data:image/png;base64," + PreviewData.projection.transmission.image;
}
}
for (let index = 0; index < LayoutImages.length; index++) {
const image = LayoutImages[index];
Expand All @@ -120,6 +127,7 @@ export function setupPreview(): void {
PreviewImages = document.querySelectorAll("img.image-projection") as NodeListOf<HTMLImageElement>;
LayoutImages = document.querySelectorAll("img.image-layout") as NodeListOf<HTMLImageElement>;
SceneImages = document.querySelectorAll("img.image-scene") as NodeListOf<HTMLImageElement>;
TransmissionCanvas = document.getElementById("previewTransmissionGraph") as HTMLCanvasElement;

// Projection image of the preview pane. Supports live updates when changing detector sizes.
PreviewPaneImage = document.querySelector("#previewImage>img.image-projection") as HTMLImageElement;
Expand All @@ -132,12 +140,14 @@ export function setupPreview(): void {
ButtonPreviewProjection.variant = "default";
PreviewPane.setAttribute("selected", "layout");
SettingsDiv.setAttribute("selected", "layout");
TransmissionCanvas.parentElement?.setAttribute("selected", "layout");
};
ButtonPreviewProjection.onclick = () => {
ButtonPreviewLayout.variant = "default";
ButtonPreviewProjection.variant = "primary";
PreviewPane.setAttribute("selected", "projection");
SettingsDiv.setAttribute("selected", "projection");
TransmissionCanvas.parentElement?.setAttribute("selected", "projection");
};

const SettingsDiv = document.getElementById("settingsPane") as HTMLDivElement;
Expand All @@ -153,12 +163,19 @@ export function setupPreview(): void {
SettingLogElement.onclick = () => {
updateImageDisplay();
};
SettingTransmissionElement = document.getElementById("radioTransmissionProjectionSetting") as SlRadio;
SettingTransmissionElement.onclick = () => {
updateImageDisplay();
};

console.log(SettingsDiv);

const PreviewSettingsButton = document.querySelector("#settingsPane > button") as HTMLButtonElement;
PreviewSettingsButton.onclick = () => {
SettingsDiv.toggleAttribute("active");

// ! workaround for chart resize issues inside flexboxes with chart.js
updateImageDisplay();
};

for (let index = 0; index < PreviewImages.length; index++) {
Expand Down
Loading