diff --git a/packages/snips/package.json b/packages/snips/package.json index 9fdebca34ef2a8753c07a53454c3121b07515efa..d2012882f31c1fcaf627ea06854230e3bafef028 100644 --- a/packages/snips/package.json +++ b/packages/snips/package.json @@ -29,6 +29,7 @@ "@types/sprintf-js": "^1.1.4", "tree-sitter-cli": "^0.25.1", "tree-sitter-python": "^0.23.6", + "skia-canvas": "catalog:", "tsup": "catalog:", "typescript": "catalog:", "vitest": "catalog:" @@ -39,7 +40,8 @@ "build": "tsup", "watch": "tsc -w", "test": "vitest", - "generate-grammars": "tree-sitter build --wasm node_modules/tree-sitter-python -o ../../assets/grammars/tree-sitter-python.wasm" + "generate-grammars": "tree-sitter build --wasm node_modules/tree-sitter-python -o ../../assets/grammars/tree-sitter-python.wasm", + "json_to_snip": "tsx ./src/json_to_snip.ts" }, "type": "module" } diff --git a/packages/snips/src/general/text.ts b/packages/snips/src/general/text.ts index f2749ea2cd5db93c161fb2d0b407e90c68b8652b..b3dbb47b98570edad6b310c892d71306003f2b41 100644 --- a/packages/snips/src/general/text.ts +++ b/packages/snips/src/general/text.ts @@ -265,15 +265,15 @@ export class TextSnip extends BaseSnip { lineWrap: validation.view?.wrap, baseline: validation.view?.baseline, //Base - id: data.id, - page_id: data.page_id, - book_id: data.book_id, - last_updated: data.last_updated, - created: data.created, - x: data.view?.x, - y: data.view?.y, - rot: data.view?.rot, - mirror: data.view?.mirror, + id: validation.id, + page_id: validation.page_id, + book_id: validation.book_id, + last_updated: validation.last_updated, + created: validation.created, + x: validation.view?.x, + y: validation.view?.y, + rot: validation.view?.rot, + mirror: validation.view?.mirror, }); } diff --git a/packages/snips/src/json_to_snip.ts b/packages/snips/src/json_to_snip.ts new file mode 100644 index 0000000000000000000000000000000000000000..ad3030eb82c5f56c4efc4c4aeb6ea8e55deab46f --- /dev/null +++ b/packages/snips/src/json_to_snip.ts @@ -0,0 +1,52 @@ +/** Debugging script to render a snippet + * + * Can be use to develop a new snippet type and test it easily. + */ + +import fs from "fs"; +import { Canvas } from "skia-canvas"; + +import { get_snip_from_data } from "./get_snip_from_data"; + +const file_name = process.argv[2]; +let output_file = process.argv[3]; + +if (!file_name) { + console.error("Usage: tsx render_json.ts <file_name> [output_file]"); + process.exit(1); +} + +if (output_file && !output_file.endsWith(".png")) { + console.error("Output file must be a PNG file"); + process.exit(1); +} + +if (!fs.existsSync(file_name)) { + console.error("File does not exist"); + process.exit(1); +} + +if (!output_file) { + console.log("No output file specified, rendering to output.png"); + output_file = "./output.png"; +} + +// Read file as JSON +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let data: any; +try { + const f = fs.readFileSync(file_name, "utf8"); + data = JSON.parse(f); +} catch (e) { + console.error(`Error reading file: ${e}`); + process.exit(1); +} + +const snip = get_snip_from_data(data); + +const canvas = new Canvas(2100, 1400); +const ctx = canvas.getContext("2d"); +snip.render(ctx as unknown as OffscreenCanvasRenderingContext2D); + +// Save to file +canvas.saveAsSync(output_file); diff --git a/packages/snips/src/uprp/__mapping.ts b/packages/snips/src/uprp/__mapping.ts index 2ba7f7d4597d10ef09754e41250a464c6e3be6f3..645fa2027b595a1d2e2adc396305ac945ffe8b4b 100644 --- a/packages/snips/src/uprp/__mapping.ts +++ b/packages/snips/src/uprp/__mapping.ts @@ -5,6 +5,7 @@ import { LogfileSnip } from "./spec/logfile"; import { MacroSpecSnip } from "./spec/macrospec"; import { MotorsSnip } from "./spec/motors"; import { TimestampSnip } from "./spec/timestamp"; +import { StampySnip } from "./stampy"; const TYPE_TO_SNIP: Map<string, typeof BaseSnip> = new Map(); @@ -25,4 +26,7 @@ TYPE_TO_SNIP.set("uprp/spec/matlab", ImageSnip); // SPOC mapping // TODO:@Markus +// Stampy +TYPE_TO_SNIP.set("uprp/stampy", StampySnip); + export default TYPE_TO_SNIP; diff --git a/packages/snips/src/uprp/stampy.ts b/packages/snips/src/uprp/stampy.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5bd5a72b79a3e16520312f7764276c0cd43928e --- /dev/null +++ b/packages/snips/src/uprp/stampy.ts @@ -0,0 +1,199 @@ +import { type } from "arktype"; + +import { DataValidationError } from "@/errors"; +import { + BaseSnip, + BaseSnipArgs, + BaseViewSchema, + RenderContext, + SnipData, + SnipDataSchema, +} from "@/general/base"; + +/* -------------------------------------------------------------------------- */ +/* Schema for insert */ +/* -------------------------------------------------------------------------- */ +// The dataschema is a one to one mapping from the stampy json +// TODO: Might need some adjustments depending on what we actually need on the snip side +// I would suggest to remove all the optional fields and only keep the required ones + +const StampySchema = type({ + meta: { + version: "number", + instrument: "string", + setup: "string", + experiment: "string", + date: "string", + }, + sample: { + name: "string", + id: "number", + comment: "string", + }, + measurement: { + volume: { + diameter: "number", + height: "number", + fovw: "number", + fovh: "number", + unit: "'mm' | 'cm'", + }, + stack: { + motor: "string", + factorh: "number", + values: "number[]", + }, + detector: { + name: "string", + pixelSize: "number", + pixels: ["number", "number"], + }, + grid: { + motors: "('cx'|'cy')[]", + method: "'hexagonal'", + factorw: "number", + prefill: "number", + values: "number[][]", + }, + timing: { + illumination: "number", + interval: "number", + unit: "'second' | 'minute' | 'hour'", + mode: "string", + topupSync: "'no' | 'yes'", + topupParameter: "number[]", + trgLine: "number", + trgDelay: "number", + }, + tomo: { + motor: "string", + frames: "number", + commands: "string[]", + angles: { + range: "number", + safety: "number", + unit: "string", + }, + velocity: { + tomo: "number", + moving: "number", + }, + }, + empty: { + frames: "number", + commands: "string[]", + scheme: "string", + pattern: "string", + }, + dark: { + frames: "number", + commands: "string[]", + }, + range: "string", + }, + motors: { + tomo: { + stx: "number", + cx: "number", + sty: "number", + cy: "number", + stz: "number", + cz: "number", + stzrot: "number", + }, + empty: { + stx: "number", + cx: "number", + sty: "number", + cy: "number", + stz: "number", + cz: "number", + stzrot: "number", + }, + final: { + stx: "number", + cx: "number", + sty: "number", + cy: "number", + stz: "number", + cz: "number", + stzrot: "number", + }, + }, +}); + +export const StampyDataSchema = type({ + "version?": type("1").describe( + "The version of the stampy snippet, empty for latest.", + ), + stampy: StampySchema.describe("The data returned from stampy."), +}); + +export const StampyViewSchema = type({}, "&", BaseViewSchema).describe( + BaseViewSchema.description, +); + +export const StampySnipSchema = type(SnipDataSchema, "&", { + data: StampyDataSchema, + "view?": StampyViewSchema, +}).describe("Represents a stampy snippet."); + +export type StampyData = typeof StampyDataSchema.infer; +export type StampyView = typeof StampyViewSchema.infer; + +export type StampySnipArgs = BaseSnipArgs & StampyData; + +/* -------------------------------------------------------------------------- */ +/* StampySnip implementation */ +/* -------------------------------------------------------------------------- */ + +export class StampySnip extends BaseSnip { + public type = "stampy"; + + stampy: StampyData["stampy"]; + + constructor({ version, stampy, ...baseArgs }: StampySnipArgs) { + super({ ...baseArgs }); + + this.stampy = stampy; + } + + static readonly _schema = StampySnipSchema; + static from_data(data: SnipData<StampyData, StampyView>): StampySnip { + // Validate + const validation = this._schema(data); + if (validation instanceof type.errors) { + throw DataValidationError.from_ark(validation); + } + + // We can check the version here if necessary + // in the future + + return new StampySnip({ + stampy: validation.data.stampy, + + // Base + id: validation.id, + page_id: validation.page_id, + book_id: validation.book_id, + last_updated: validation.last_updated, + created: validation.created, + x: validation.view?.x, + y: validation.view?.y, + rot: validation.view?.rot, + mirror: validation.view?.mirror, + }); + } + + // TODO: Implement this method + get width(): number { + return 0; + } + get height(): number { + return 0; + } + + public render(ctx: RenderContext): void { + throw new Error("Render method not implemented."); + } +} diff --git a/packages/snips/stampy_example_snip.json b/packages/snips/stampy_example_snip.json new file mode 100644 index 0000000000000000000000000000000000000000..d6b21bd012c3e795eeffba7cd604d61ceccb96bc --- /dev/null +++ b/packages/snips/stampy_example_snip.json @@ -0,0 +1,100 @@ +{ + "type": "uprp/stampy", + "book_id": -1, + "data": { + "stampy": { + "meta": { + "version": 1, + "instrument": "GINIX", + "setup": "PB tomo", + "experiment": "run123", + "date": "2024-9-27" + }, + "sample": { + "name": "sample01a", + "id": 0, + "comment": "NR_PREV\\nImaging of top part" + }, + "measurement": { + "volume": { + "diameter": 1, + "height": 1, + "fovw": 1.4, + "fovh": 1.2, + "unit": "mm" + }, + "stack": { "motor": "stz", "factorh": 1.1, "values": [0] }, + "detector": { + "name": "PCO.edge", + "pixelSize": 0.65e-6, + "pixels": [2560, 2160] + }, + "grid": { + "motors": ["cx", "cy"], + "method": "hexagonal", + "factorw": 1.1, + "prefill": 1, + "values": [[0, 0]] + }, + "timing": { + "illumination": 0.035, + "interval": 0.015, + "unit": "second", + "mode": "continuous-1", + "topupSync": "no", + "topupParameter": [100.9, 100.8], + "trgLine": 2, + "trgDelay": 2000 + }, + "tomo": { + "motor": "stzrot", + "frames": 3000, + "commands": ["oslow; sleep(1);", "cslow;"], + "angles": { + "range": 360.014, + "safety": 20, + "unit": "degree" + }, + "velocity": { "tomo": 11972, "moving": 240000 } + }, + "empty": { + "frames": 500, + "commands": ["", ""], + "scheme": "pre-volume", + "pattern": "eted" + }, + "dark": { "frames": 100, "commands": ["cslow; sleep(1);", ""] }, + "range": "all" + }, + "motors": { + "tomo": { + "stx": 180, + "cx": 2.50001, + "sty": -3.94607, + "cy": 1.60297, + "stz": -1, + "cz": 2.07352, + "stzrot": 0.000194 + }, + "empty": { + "stx": 180, + "cx": 2.50001, + "sty": -3.94607, + "cy": -4, + "stz": -1, + "cz": 2.07352, + "stzrot": 0.000194 + }, + "final": { + "stx": 180, + "cx": 2.50001, + "sty": -3.94607, + "cy": 1.60297, + "stz": -1, + "cz": 2.07352, + "stzrot": 0.000194 + } + } + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c20323a78910cbb22f0c524345f02fbd84421bb2..89c15e72a4624d335c0e3d5fc2c8be312fb5ee2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1013,6 +1013,9 @@ importers: '@types/sprintf-js': specifier: ^1.1.4 version: 1.1.4 + skia-canvas: + specifier: 'catalog:' + version: 2.0.2 tree-sitter-cli: specifier: ^0.25.1 version: 0.25.1