diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/input-parameters-python.js b/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/input-parameters-python.js index 417ec9fa1..c7aa96cde 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/input-parameters-python.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/input-parameters-python.js @@ -1,4 +1,4 @@ -import snakeCase from "../snake-case.js" +import snakeCase from '../snake-case.js' function inputParametersPython(functionName, indent, parameter, required) { let initResult = '' @@ -54,7 +54,9 @@ function inputParametersPython(functionName, indent, parameter, required) { methodResult += `${indent} self.model.${modelProperty}['${parameterName}'] = int(self.${inputIdentifier}.value)\n\n` break default: - console.error(`Unexpected interface type: ${parameter.type}`) + console.error( + `inputParametersPython: Unexpected interface type: ${parameter.type}` + ) process.exit(1) } return { init: initResult, method: methodResult } diff --git a/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/output-python.js b/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/output-python.js index 3c307c4f8..758efc0e3 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/output-python.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/python-web-demo/output-python.js @@ -1,4 +1,4 @@ -import snakeCase from "../snake-case.js" +import snakeCase from '../snake-case.js' function outputPython(functionName, prefix, indent, parameter) { const parameterName = snakeCase(parameter.name) @@ -6,13 +6,13 @@ function outputPython(functionName, prefix, indent, parameter) { let methodResult = '' let runResult = '' - switch(parameter.type) { + switch (parameter.type) { // case 'OUTPUT_TEXT_FILE:FILE': // case 'OUTPUT_TEXT_STREAM': - // result += `${indent}\n` - // result += `${indent}${snakeCase(parameter.name)}\n` - // result += `

\n` - // break + // result += `${indent}\n` + // result += `${indent}${snakeCase(parameter.name)}\n` + // result += `

\n` + // break case 'OUTPUT_BINARY_FILE:FILE': case 'OUTPUT_BINARY_STREAM': initResult += `${indent} ${parameterName}_download_element = js.document.querySelector('#${functionName}-outputs sl-button[name=${parameter.name}-download]')\n` @@ -53,7 +53,9 @@ function outputPython(functionName, prefix, indent, parameter) { // result += `

\n` // break default: - console.error(`Unexpected interface type: ${parameter.type}`) + console.error( + `outputPython: Unexpected interface type: ${parameter.type}` + ) process.exit(1) } return { init: initResult, method: methodResult, run: runResult } diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/all-demo-types-supported.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/all-demo-types-supported.js index 62bcd99a6..37b19990e 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/all-demo-types-supported.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/all-demo-types-supported.js @@ -12,7 +12,8 @@ const demoSupportedInputTypes = new Set([ 'INPUT_JSON', 'INPUT_IMAGE', 'INPUT_MESH', - 'INPUT_POINT_SET' + 'INPUT_POINT_SET', + 'INPUT_TRANSFORM' ]) const demoSupportedOutputTypes = new Set([ 'OUTPUT_TEXT_FILE', @@ -22,7 +23,8 @@ const demoSupportedOutputTypes = new Set([ 'OUTPUT_JSON', 'OUTPUT_IMAGE', 'OUTPUT_MESH', - 'OUTPUT_POINT_SET' + 'OUTPUT_POINT_SET', + 'OUTPUT_TRANSFORM' ]) function allDemoTypesSupported(interfaceJson) { diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-html.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-html.js index 30a9c2453..8868c8bb7 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-html.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-html.js @@ -67,6 +67,7 @@ function inputParametersDemoHtml( case 'INPUT_JSON': case 'INPUT_MESH': case 'INPUT_POINT_SET': + case 'INPUT_TRANSFORM': result += `${prefix}${indent}\n` result += `${prefix}${indent}\n` result += '

\n' @@ -76,8 +77,11 @@ function inputParametersDemoHtml( result += `${prefix}${indent}\n` result += '

\n' break + break default: - console.error(`Unexpected interface type: ${parameterType}`) + console.error( + `inputParametersDemoHtml: Unexpected interface type: ${parameterType}` + ) process.exit(1) } return result diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-typescript.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-typescript.js index 2a99f3a6c..cf0b11d84 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-typescript.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/input-parameters-demo-typescript.js @@ -114,6 +114,7 @@ function inputParametersDemoTypeScript( case 'INPUT_IMAGE': case 'INPUT_MESH': case 'INPUT_POINT_SET': + case 'INPUT_TRANSFORM': result += `${indent}const ${inputIdentifier} = document.querySelector('#${functionName}Inputs input[name=${parameter.name}-file]')\n` result += `${indent}${inputIdentifier}.addEventListener('change', async (event) => {\n` result += `${indent}${indent}const dataTransfer = event.dataTransfer\n` @@ -172,12 +173,29 @@ function inputParametersDemoTypeScript( result += `${indent}${indent}const details = document.getElementById("${functionName}-${parameter.name}-details")\n` result += `${indent}${indent}details.innerHTML = \`
$\{globalThis.escapeHtml(JSON.stringify(pointSet, globalThis.interfaceTypeJsonReplacer, 2))}
\`\n` } + } else if (parameterType === 'INPUT_TRANSFORM') { + if (parameter.itemsExpectedMax > 1) { + result += `${indent}${indent}const readTransform = await Promise.all(Array.from(files).map(async (file) => readTransform(file)))\n` + result += `${indent}${indent}readTransform.forEach(t => t.webWorker.terminate())\n` + result += `${indent}${indent}const inputTransform = readTransform.map(t => t.transform)\n` + result += `${indent}${indent}model.${modelProperty}.set("${parameterName}", inputTransform)\n` + result += `${indent}${indent}const details = document.getElementById("${functionName}-${parameter.name}-details")\n` + result += `${indent}${indent}details.innerHTML = \`
$\{globalThis.escapeHtml(JSON.stringify(inputTransform, globalThis.interfaceTypeJsonReplacer, 2))}
\`\n` + } else { + result += `${indent}${indent}const { transform, webWorker } = await readTransform(files[0])\n` + result += `${indent}${indent}webWorker.terminate()\n` + result += `${indent}${indent}model.${modelProperty}.set("${parameterName}", transform)\n` + result += `${indent}${indent}const details = document.getElementById("${functionName}-${parameter.name}-details")\n` + result += `${indent}${indent}details.innerHTML = \`
$\{globalThis.escapeHtml(JSON.stringify(transform, globalThis.interfaceTypeJsonReplacer, 2))}
\`\n` + } } result += `${indent}${indent}details.disabled = false\n` result += `${indent}})\n\n` break default: - console.error(`Unexpected interface type: ${parameterType}`) + console.error( + `inputParametersDemoTypeScript: Unexpected interface type: ${parameterType}` + ) process.exit(1) } return result diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/interface-functions-demo-typescript.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/interface-functions-demo-typescript.js index ef99aaa58..397af9116 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/interface-functions-demo-typescript.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/interface-functions-demo-typescript.js @@ -24,9 +24,11 @@ function interfaceFunctionsDemoTypeScript( needReadImage, needReadMesh, needReadPointSet, + needReadTransform, needWriteImage, needWriteMesh, - needWritePointSet + needWritePointSet, + needWriteTransform } = ioPackagesNeeded(interfaceJson) if (needReadMesh) { if (packageName === '@itk-wasm/mesh-io') { @@ -49,6 +51,13 @@ function interfaceFunctionsDemoTypeScript( result += `import { readImage } from '@itk-wasm/image-io'\n` } } + if (needReadTransform) { + if (packageName === '@itk-wasm/transform-io') { + result += `import { readTransform } from '../../../dist/index.js'\n` + } else { + result += `import { readTransform } from '@itk-wasm/transform-io'\n` + } + } if (needWriteMesh) { if (packageName === '@itk-wasm/mesh-io') { result += `import { writeMesh } from '../../../dist/index.js'\n` @@ -70,6 +79,13 @@ function interfaceFunctionsDemoTypeScript( result += `import { writeImage } from '@itk-wasm/image-io'\n` } } + if (needWriteTransform) { + if (packageName === '@itk-wasm/transform-io') { + result += `import { writeTransform } from '../../../dist/index.js'\n` + } else { + result += `import { writeTransform } from '@itk-wasm/transform-io'\n` + } + } result += `import * as ${camelCase(bundleName)} from '../../../dist/index.js'\n` diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/io-packages-needed.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/io-packages-needed.js index 9c431385d..44caf6fb3 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/io-packages-needed.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/io-packages-needed.js @@ -4,6 +4,7 @@ function ioPackagesNeeded(interfaceJson) { let needReadMesh = false let needReadImage = false let needReadPointSet = false + let needReadTransform = false const pipelineComponents = ['inputs', 'parameters'] pipelineComponents.forEach((pipelineComponent) => { needReadMesh = @@ -22,6 +23,12 @@ function ioPackagesNeeded(interfaceJson) { interfaceJson[pipelineComponent].filter( (value) => interfaceJsonTypeToInterfaceType.get(value.type) === 'Image' ).length > 0 + needReadTransform = + needReadTransform || + interfaceJson[pipelineComponent].filter( + (value) => + interfaceJsonTypeToInterfaceType.get(value.type) === 'TransformList' + ).length > 0 }) const needWriteMesh = interfaceJson.outputs.filter( @@ -35,13 +42,20 @@ function ioPackagesNeeded(interfaceJson) { interfaceJson.outputs.filter( (value) => interfaceJsonTypeToInterfaceType.get(value.type) === 'Image' ).length > 0 + const needWriteTransform = + interfaceJson.outputs.filter( + (value) => + interfaceJsonTypeToInterfaceType.get(value.type) === 'TransformList' + ).length > 0 return { needReadImage, needReadMesh, needReadPointSet, + needReadTransform, needWriteImage, needWriteMesh, - needWritePointSet + needWritePointSet, + needWriteTransform } } diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-html.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-html.js index d047b5b04..43810448e 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-html.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-html.js @@ -96,8 +96,24 @@ function outputDemoHtml(functionName, prefix, indent, parameter) { result += `

\n` } break + case 'OUTPUT_TRANSFORM': + { + result += `${prefix}${indent}\n` + + result += `${prefix}${indent}\n` + const formats = ['h5', 'txt', 'mat', 'xfm'] + formats.forEach((format) => { + result += `${prefix}${indent}${indent}${format}\n` + }) + result += `${prefix}${indent}\n` + result += `${prefix}${indent}Download\n` + result += `

\n` + } + break default: - console.error(`Unexpected interface type: ${parameterType}`) + console.error( + `outputDemoHtml: Unexpected interface type: ${parameterType}` + ) process.exit(1) } return result diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-run-typescript.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-run-typescript.js index 085e69358..4d4821c51 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-run-typescript.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-run-typescript.js @@ -48,6 +48,7 @@ function outputDemoRunTypeScript(functionName, prefix, indent, parameter) { case 'OUTPUT_IMAGE': case 'OUTPUT_MESH': case 'OUTPUT_POINT_SET': + case 'OUTPUT_TRANSFORM': result += `${prefix}${indent}${parameterName}OutputDownload.variant = "success"\n` result += `${prefix}${indent}${parameterName}OutputDownload.disabled = false\n` result += `${indent}${indent}const ${parameterName}Details = document.getElementById("${functionName}-${parameter.name}-details")\n` @@ -62,7 +63,9 @@ function outputDemoRunTypeScript(functionName, prefix, indent, parameter) { } break default: - console.error(`Unexpected interface type: ${parameter.type}`) + console.error( + `outputDemoRunTypeScript: Unexpected interface type: ${parameter.type}` + ) process.exit(1) } return result diff --git a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-typescript.js b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-typescript.js index e9e710e2b..f66de59f4 100644 --- a/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-typescript.js +++ b/packages/core/typescript/itk-wasm/src/bindgen/typescript/demo/output-demo-typescript.js @@ -102,8 +102,25 @@ function outputDemoTypeScript(functionName, prefix, indent, parameter) { result += `${prefix}${indent}${indent}}\n` result += `${prefix}${indent}})\n` break + case 'OUTPUT_TRANSFORM': + result += `${prefix}${indent}const ${parameterName}OutputDownload = document.querySelector('#${functionName}Outputs sl-button[name=${parameter.name}-download]')\n` + result += `${prefix}${indent}${parameterName}OutputDownload.addEventListener('click', async (event) => {\n` + result += `${prefix}${indent}${indent}event.preventDefault()\n` + result += `${prefix}${indent}${indent}event.stopPropagation()\n` + result += `${prefix}${indent}${indent}if (model.outputs.has("${parameterName}")) {\n` + result += `${prefix}${indent}${indent}${indent}const ${parameterName}DownloadFormat = document.getElementById('${functionName}-${parameter.name}-output-format')\n` + result += `${prefix}${indent}${indent}${indent}const downloadFormat = ${parameterName}DownloadFormat.value || 'nrrd'\n` + result += `${prefix}${indent}${indent}${indent}const fileName = \`${parameterName}.\${downloadFormat}\`\n` + result += `${prefix}${indent}${indent}${indent}const { webWorker, serializedTransform } = await writeImage(model.outputs.get("${parameterName}"), fileName)\n\n` + result += `${prefix}${indent}${indent}${indent}webWorker.terminate()\n` + result += `${prefix}${indent}${indent}${indent}globalThis.downloadFile(serializedTransform.data, fileName)\n` + result += `${prefix}${indent}${indent}}\n` + result += `${prefix}${indent}})\n` + break default: - console.error(`Unexpected interface type: ${parameter.type}`) + console.error( + `outputDemoTypeScript: Unexpected interface type: ${parameter.type}` + ) process.exit(1) } return result diff --git a/packages/transform-io/typescript/.gitignore b/packages/transform-io/typescript/.gitignore new file mode 100644 index 000000000..63d1828c7 --- /dev/null +++ b/packages/transform-io/typescript/.gitignore @@ -0,0 +1 @@ +cypress/screenshots/ diff --git a/packages/transform-io/typescript/cypress.config.ts b/packages/transform-io/typescript/cypress.config.ts new file mode 100644 index 000000000..1d4054ee4 --- /dev/null +++ b/packages/transform-io/typescript/cypress.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + defaultCommandTimeout: 40000, + setupNodeEvents(on, config) { + }, + }, +}); diff --git a/packages/transform-io/typescript/cypress/e2e/common.ts b/packages/transform-io/typescript/cypress/e2e/common.ts new file mode 100644 index 000000000..0c496487a --- /dev/null +++ b/packages/transform-io/typescript/cypress/e2e/common.ts @@ -0,0 +1,24 @@ +export const demoServer = "http://localhost:5181"; + +export function verifyTestLinearTransform(transformList) { + cy.expect(transformList.length).to.equal(1); + const transform = transformList[0]; + cy.expect(transform.transformType.transformParameterization).to.equal( + "Affine" + ); + cy.expect(transform.transformType.parametersValueType).to.equal("float64"); + cy.expect(transform.transformType.inputDimension).to.equal(3); + cy.expect(transform.transformType.outputDimension).to.equal(3); + cy.expect(transform.numberOfParameters).to.equal(12); + cy.expect(transform.numberOfFixedParameters).to.equal(3); + cy.deepEqual(transform.fixedParameters, new Float64Array([0, 0, 0])); + cy.deepEqual( + transform.parameters, + new Float64Array([ + 0.65631490118447, 0.5806583745824385, -0.4817536741017158, + -0.7407986817430222, 0.37486398378429736, -0.5573995934598175, + -0.14306664045479867, 0.7227121458012518, 0.676179776908723, + -65.99999999999997, 69.00000000000004, 32.000000000000036, + ]) + ); +} diff --git a/packages/transform-io/typescript/cypress/e2e/read-transform.ty.ts b/packages/transform-io/typescript/cypress/e2e/read-transform.ty.ts new file mode 100644 index 000000000..c2ea6c47a --- /dev/null +++ b/packages/transform-io/typescript/cypress/e2e/read-transform.ty.ts @@ -0,0 +1,72 @@ +import { demoServer, verifyMesh } from './common.ts' + +describe('read-mesh', () => { + beforeEach(function() { + cy.visit(demoServer) + + const testPathPrefix = '../test/data/input/' + + const testImageFiles = [ + 'cow.vtk' + ] + testImageFiles.forEach((fileName) => { + cy.readFile(`${testPathPrefix}${fileName}`, null).as(fileName) + }) + }) + + it('Reads an mesh File in the demo', function () { + cy.get('sl-tab[panel="readMesh-panel"]').click() + + const testFile = { contents: new Uint8Array(this['cow.vtk']), fileName: 'cow.vtk' } + cy.get('#readMeshInputs input[name="serialized-mesh-file"]').selectFile([testFile,], { force: true }) + cy.get('#readMesh-serialized-mesh-details').should('contain', '35,32') + + cy.get('#readMeshInputs sl-button[name="run"]').click() + + cy.get('#readMesh-mesh-details').should('contain', 'meshType') + }) + + it('Reads an mesh BinaryFile', function () { + cy.window().then(async (win) => { + const arrayBuffer = new Uint8Array(this['cow.vtk']).buffer + const { mesh, webWorker } = await win.meshIo.readMesh({ data: new Uint8Array(arrayBuffer), path: 'cow.vtk' }) + webWorker.terminate() + verifyMesh(mesh) + }) + }) + + it('Reads an mesh File', function () { + cy.window().then(async (win) => { + const arrayBuffer = new Uint8Array(this['cow.vtk']).buffer + const cowFile = new win.File([arrayBuffer], 'cow.vtk') + const { mesh, webWorker } = await win.meshIo.readMesh(cowFile) + webWorker.terminate() + verifyMesh(mesh) + }) + }) + + it('Reads re-uses a WebWorker', function () { + cy.window().then(async (win) => { + const arrayBuffer = new Uint8Array(this['cow.vtk']).buffer + const cowFile = new win.File([arrayBuffer], 'cow.vtk') + const { webWorker } = await win.meshIo.readMesh(cowFile) + const { mesh } = await win.meshIo.readMesh(cowFile, { webWorker }) + webWorker.terminate() + verifyMesh(mesh) + }) + }) + + it('Throws a catchable error for an invalid file', { defaultCommandTimeout: 120000 }, function () { + cy.window().then(async (win) => { + const invalidArray = new Uint8Array([21, 4, 4, 4, 4, 9, 5, 0, 82, 42]) + const invalidBlob = new win.Blob([invalidArray]) + const invalidFile = new win.File([invalidBlob], 'invalid.file') + try { + const { webWorker, mesh } = await win.meshIo.readMesh(invalidFile) + webWorker.terminate() + } catch (error) { + cy.expect(error.message).to.equal('Could not find IO for: invalid.file') + } + }) + }) +}) diff --git a/packages/transform-io/typescript/cypress/e2e/write-transform.cy.ts b/packages/transform-io/typescript/cypress/e2e/write-transform.cy.ts new file mode 100644 index 000000000..389635247 --- /dev/null +++ b/packages/transform-io/typescript/cypress/e2e/write-transform.cy.ts @@ -0,0 +1,52 @@ +import { demoServer, verifyMesh } from "./common.ts"; + +describe("write-mesh", () => { + beforeEach(function () { + cy.visit(demoServer); + + const testPathPrefix = "../test/data/input/"; + + const testMeshFiles = ["cow.iwm.cbor"]; + testMeshFiles.forEach((fileName) => { + cy.readFile(`${testPathPrefix}${fileName}`, null).as(fileName); + }); + }); + + it("Writes an mesh in the demo", function () { + cy.get('sl-tab[panel="writeMesh-panel"]').click(); + + const testFile = { + contents: new Uint8Array(this["cow.iwm.cbor"]), + fileName: "cow.iwm.cbor", + }; + cy.get('#writeMeshInputs input[name="mesh-file"]').selectFile([testFile], { + force: true, + }); + cy.get("#writeMesh-mesh-details").should("contain", "meshType"); + cy.get('#writeMeshInputs sl-input[name="serialized-mesh"]') + .find("input", { includeShadowDom: true }) + .type("cow.vtk", { force: true }); + + cy.get('#writeMeshInputs sl-button[name="run"]').click(); + + cy.get("#writeMesh-serialized-mesh-details").should("contain", "35,32"); + }); + + it("Writes an mesh to an ArrayBuffer", function () { + cy.window().then(async (win) => { + const arrayBuffer = new Uint8Array(this["cow.iwm.cbor"]).buffer; + const { mesh, webWorker } = await win.meshIo.readMesh({ + data: new Uint8Array(arrayBuffer), + path: "cow.iwm.cbor", + }); + const { serializedMesh } = await win.meshIo.writeMesh(mesh, "cow.vtk", { + webWorker, + }); + const { mesh: meshBack } = await win.meshIo.readMesh(serializedMesh, { + webWorker, + }); + webWorker.terminate(); + verifyMesh(meshBack); + }); + }); +}); diff --git a/packages/transform-io/typescript/cypress/support/commands.ts b/packages/transform-io/typescript/cypress/support/commands.ts new file mode 100644 index 000000000..698b01a42 --- /dev/null +++ b/packages/transform-io/typescript/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } \ No newline at end of file diff --git a/packages/transform-io/typescript/cypress/support/e2e.ts b/packages/transform-io/typescript/cypress/support/e2e.ts new file mode 100644 index 000000000..ca171c17d --- /dev/null +++ b/packages/transform-io/typescript/cypress/support/e2e.ts @@ -0,0 +1,31 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') + +Cypress.on('uncaught:exception', (err, runnable) => { + // we expect a 3rd party library error with message 'list not defined' + // and don't want to fail the test so we return false + if (err.message.includes('ResizeObserver loop completed with undelivered notifications')) { + return false + } + // we still want to ensure there are no other unexpected + // errors, so we let them fail the test +}) + diff --git a/packages/transform-io/typescript/cypress/tsconfig.json b/packages/transform-io/typescript/cypress/tsconfig.json new file mode 100644 index 000000000..212405809 --- /dev/null +++ b/packages/transform-io/typescript/cypress/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "include": [ + "**/*.ts" + ], + "compilerOptions": { + "noEmit": false, + "sourceMap": false, + "inlineSourceMap": true, + "types": ["cypress"] + }, +} diff --git a/packages/transform-io/typescript/package.json b/packages/transform-io/typescript/package.json index a12f5ad0d..0bae2268e 100644 --- a/packages/transform-io/typescript/package.json +++ b/packages/transform-io/typescript/package.json @@ -15,8 +15,15 @@ }, "scripts": { "start": "pnpm copyDemoAppAssets && vite", - "test": "pnpm test:node", + "test": "pnpm test:node && pnpm test:browser", "test:node": "ava", + "test:browser": "pnpm test:browser:chrome && pnpm test:browser:firefox", + "test:browser:firefox": "start-server-and-test start http-get://localhost:5181 cypress:runFirefox", + "test:browser:chrome": "start-server-and-test start http-get://localhost:5181 cypress:runChrome", + "test:browser:debug": "start-server-and-test start http-get://localhost:5181 cypress:open", + "cypress:open": "pnpm exec cypress open", + "cypress:runChrome": "pnpm exec cypress run --browser chrome", + "cypress:runFirefox": "pnpm exec cypress run --browser firefox", "build": "pnpm build:tsc && pnpm build:browser:workerEmbedded && pnpm build:browser:workerEmbeddedMin && pnpm build:demo", "build:browser:workerEmbedded": "esbuild --loader:.worker.js=dataurl --bundle --format=esm --outfile=./dist/bundle/index-worker-embedded.js ./src/index-worker-embedded.ts", "build:browser:workerEmbeddedMin": "esbuild --minify --loader:.worker.js=dataurl --bundle --format=esm --outfile=./dist/bundle/index-worker-embedded.min.js ./src/index-worker-embedded.min.ts", @@ -40,11 +47,13 @@ "devDependencies": { "@itk-wasm/demo-app": "workspace:*", "@itk-wasm/transform-io-build": "workspace:*", - "@types/node": "^20.2.5", "@types/mime-types": "^2.1.4", + "@types/node": "^20.2.5", "ava": "^6.1.3", + "cypress": "^13.11.0", "esbuild": "^0.19.8", "shx": "^0.3.4", + "start-server-and-test": "^2.0.4", "typescript": "^5.3.2", "vite": "^4.5.0", "vite-plugin-static-copy": "^0.17.0" diff --git a/packages/transform-io/typescript/src/index-only.ts b/packages/transform-io/typescript/src/index-only.ts index 080570398..c5805f984 100644 --- a/packages/transform-io/typescript/src/index-only.ts +++ b/packages/transform-io/typescript/src/index-only.ts @@ -1,125 +1,123 @@ -// Generated file. To retain edits, remove this comment. +import ReadTransformResult from "./read-transform-result.js"; +export type { ReadTransformResult }; -export * from './pipelines-base-url.js' -export * from './pipeline-worker-url.js' -export * from './default-web-worker.js' +import readTransform from "./read-transform.js"; +export { readTransform }; +import WriteTransformResult from "./write-transform-result.js"; +export type { WriteTransformResult }; -import Hdf5ReadTransformResult from './hdf5-read-transform-result.js' -export type { Hdf5ReadTransformResult } +import writeTransform from "./write-transform.js"; +export { writeTransform }; -import Hdf5ReadTransformOptions from './hdf5-read-transform-options.js' -export type { Hdf5ReadTransformOptions } +export * from "./pipelines-base-url.js"; +export * from "./pipeline-worker-url.js"; +export * from "./default-web-worker.js"; -import hdf5ReadTransform from './hdf5-read-transform.js' -export { hdf5ReadTransform } +import Hdf5ReadTransformResult from "./hdf5-read-transform-result.js"; +export type { Hdf5ReadTransformResult }; +import Hdf5ReadTransformOptions from "./hdf5-read-transform-options.js"; +export type { Hdf5ReadTransformOptions }; -import Hdf5WriteTransformResult from './hdf5-write-transform-result.js' -export type { Hdf5WriteTransformResult } +import hdf5ReadTransform from "./hdf5-read-transform.js"; +export { hdf5ReadTransform }; -import Hdf5WriteTransformOptions from './hdf5-write-transform-options.js' -export type { Hdf5WriteTransformOptions } +import Hdf5WriteTransformResult from "./hdf5-write-transform-result.js"; +export type { Hdf5WriteTransformResult }; -import hdf5WriteTransform from './hdf5-write-transform.js' -export { hdf5WriteTransform } +import Hdf5WriteTransformOptions from "./hdf5-write-transform-options.js"; +export type { Hdf5WriteTransformOptions }; +import hdf5WriteTransform from "./hdf5-write-transform.js"; +export { hdf5WriteTransform }; -import MatReadTransformResult from './mat-read-transform-result.js' -export type { MatReadTransformResult } +import MatReadTransformResult from "./mat-read-transform-result.js"; +export type { MatReadTransformResult }; -import MatReadTransformOptions from './mat-read-transform-options.js' -export type { MatReadTransformOptions } +import MatReadTransformOptions from "./mat-read-transform-options.js"; +export type { MatReadTransformOptions }; -import matReadTransform from './mat-read-transform.js' -export { matReadTransform } +import matReadTransform from "./mat-read-transform.js"; +export { matReadTransform }; +import MatWriteTransformResult from "./mat-write-transform-result.js"; +export type { MatWriteTransformResult }; -import MatWriteTransformResult from './mat-write-transform-result.js' -export type { MatWriteTransformResult } +import MatWriteTransformOptions from "./mat-write-transform-options.js"; +export type { MatWriteTransformOptions }; -import MatWriteTransformOptions from './mat-write-transform-options.js' -export type { MatWriteTransformOptions } +import matWriteTransform from "./mat-write-transform.js"; +export { matWriteTransform }; -import matWriteTransform from './mat-write-transform.js' -export { matWriteTransform } +import MncReadTransformResult from "./mnc-read-transform-result.js"; +export type { MncReadTransformResult }; +import MncReadTransformOptions from "./mnc-read-transform-options.js"; +export type { MncReadTransformOptions }; -import MncReadTransformResult from './mnc-read-transform-result.js' -export type { MncReadTransformResult } +import mncReadTransform from "./mnc-read-transform.js"; +export { mncReadTransform }; -import MncReadTransformOptions from './mnc-read-transform-options.js' -export type { MncReadTransformOptions } +import MncWriteTransformResult from "./mnc-write-transform-result.js"; +export type { MncWriteTransformResult }; -import mncReadTransform from './mnc-read-transform.js' -export { mncReadTransform } +import MncWriteTransformOptions from "./mnc-write-transform-options.js"; +export type { MncWriteTransformOptions }; +import mncWriteTransform from "./mnc-write-transform.js"; +export { mncWriteTransform }; -import MncWriteTransformResult from './mnc-write-transform-result.js' -export type { MncWriteTransformResult } +import TxtReadTransformResult from "./txt-read-transform-result.js"; +export type { TxtReadTransformResult }; -import MncWriteTransformOptions from './mnc-write-transform-options.js' -export type { MncWriteTransformOptions } +import TxtReadTransformOptions from "./txt-read-transform-options.js"; +export type { TxtReadTransformOptions }; -import mncWriteTransform from './mnc-write-transform.js' -export { mncWriteTransform } +import txtReadTransform from "./txt-read-transform.js"; +export { txtReadTransform }; +import TxtWriteTransformResult from "./txt-write-transform-result.js"; +export type { TxtWriteTransformResult }; -import TxtReadTransformResult from './txt-read-transform-result.js' -export type { TxtReadTransformResult } +import TxtWriteTransformOptions from "./txt-write-transform-options.js"; +export type { TxtWriteTransformOptions }; -import TxtReadTransformOptions from './txt-read-transform-options.js' -export type { TxtReadTransformOptions } +import txtWriteTransform from "./txt-write-transform.js"; +export { txtWriteTransform }; -import txtReadTransform from './txt-read-transform.js' -export { txtReadTransform } +import WasmReadTransformResult from "./wasm-read-transform-result.js"; +export type { WasmReadTransformResult }; +import WasmReadTransformOptions from "./wasm-read-transform-options.js"; +export type { WasmReadTransformOptions }; -import TxtWriteTransformResult from './txt-write-transform-result.js' -export type { TxtWriteTransformResult } +import wasmReadTransform from "./wasm-read-transform.js"; +export { wasmReadTransform }; -import TxtWriteTransformOptions from './txt-write-transform-options.js' -export type { TxtWriteTransformOptions } +import WasmWriteTransformResult from "./wasm-write-transform-result.js"; +export type { WasmWriteTransformResult }; -import txtWriteTransform from './txt-write-transform.js' -export { txtWriteTransform } +import WasmWriteTransformOptions from "./wasm-write-transform-options.js"; +export type { WasmWriteTransformOptions }; +import wasmWriteTransform from "./wasm-write-transform.js"; +export { wasmWriteTransform }; -import WasmReadTransformResult from './wasm-read-transform-result.js' -export type { WasmReadTransformResult } +import WasmZstdReadTransformResult from "./wasm-zstd-read-transform-result.js"; +export type { WasmZstdReadTransformResult }; -import WasmReadTransformOptions from './wasm-read-transform-options.js' -export type { WasmReadTransformOptions } +import WasmZstdReadTransformOptions from "./wasm-zstd-read-transform-options.js"; +export type { WasmZstdReadTransformOptions }; -import wasmReadTransform from './wasm-read-transform.js' -export { wasmReadTransform } +import wasmZstdReadTransform from "./wasm-zstd-read-transform.js"; +export { wasmZstdReadTransform }; +import WasmZstdWriteTransformResult from "./wasm-zstd-write-transform-result.js"; +export type { WasmZstdWriteTransformResult }; -import WasmWriteTransformResult from './wasm-write-transform-result.js' -export type { WasmWriteTransformResult } +import WasmZstdWriteTransformOptions from "./wasm-zstd-write-transform-options.js"; +export type { WasmZstdWriteTransformOptions }; -import WasmWriteTransformOptions from './wasm-write-transform-options.js' -export type { WasmWriteTransformOptions } - -import wasmWriteTransform from './wasm-write-transform.js' -export { wasmWriteTransform } - - -import WasmZstdReadTransformResult from './wasm-zstd-read-transform-result.js' -export type { WasmZstdReadTransformResult } - -import WasmZstdReadTransformOptions from './wasm-zstd-read-transform-options.js' -export type { WasmZstdReadTransformOptions } - -import wasmZstdReadTransform from './wasm-zstd-read-transform.js' -export { wasmZstdReadTransform } - - -import WasmZstdWriteTransformResult from './wasm-zstd-write-transform-result.js' -export type { WasmZstdWriteTransformResult } - -import WasmZstdWriteTransformOptions from './wasm-zstd-write-transform-options.js' -export type { WasmZstdWriteTransformOptions } - -import wasmZstdWriteTransform from './wasm-zstd-write-transform.js' -export { wasmZstdWriteTransform } +import wasmZstdWriteTransform from "./wasm-zstd-write-transform.js"; +export { wasmZstdWriteTransform }; diff --git a/packages/transform-io/typescript/src/read-transform-result.ts b/packages/transform-io/typescript/src/read-transform-result.ts new file mode 100644 index 000000000..72615842c --- /dev/null +++ b/packages/transform-io/typescript/src/read-transform-result.ts @@ -0,0 +1,8 @@ +import { TransformList } from "itk-wasm"; + +interface ReadTransformResult { + transform: TransformList; + webWorker: Worker; +} + +export default ReadTransformResult; diff --git a/packages/transform-io/typescript/src/read-transform.ts b/packages/transform-io/typescript/src/read-transform.ts new file mode 100644 index 000000000..01a35dc3a --- /dev/null +++ b/packages/transform-io/typescript/src/read-transform.ts @@ -0,0 +1,112 @@ +import { + BinaryFile, + TransformList, + getFileExtension, + WorkerPoolFunctionResult, + WorkerPoolFunctionOption, +} from "itk-wasm"; + +import mimeToTransformIo from "./mime-to-transform-io.js"; +import extensionToTransformIo from "./extension-to-transform-io.js"; +import transformIoIndex from "./transform-io-index.js"; + +import ReadTransformOptions from "./read-transform-options.js"; +import ReadTransformResult from "./read-transform-result.js"; + +interface ReaderResult extends WorkerPoolFunctionResult { + couldRead: boolean; + transform: TransformList; +} +interface ReaderOptions extends WorkerPoolFunctionOption { + /** Use float for the parameters value type. The default is double. */ + floatParameters?: boolean; +} +type Reader = ( + serializedTransform: File | BinaryFile, + options: ReaderOptions +) => Promise; + +/** + * Read a transform file format and convert it to the ITK-Wasm file format + * + * @param {webWorker} null | webWorker - Web worker to run the pipeline or null to run it in a new worker + * @param {File | BinaryFile} serializedTransform - Input transform serialized in the file format + * @param {ReadTransformOptions} options - options to cast the resulting transform type or to only read transform metadata + * + * @returns {Promise} - result object with the transform and the web worker used + */ +async function readTransform( + serializedTransform: File | BinaryFile, + options: ReadTransformOptions = {} +): Promise { + const mimeType = (serializedTransform as File).type ?? ""; + const fileName = + (serializedTransform as File).name ?? + (serializedTransform as BinaryFile).path ?? + "fileName"; + const extension = getFileExtension(fileName).toLowerCase(); + let usedWebWorker = options?.webWorker; + + let serializedTransformFile = serializedTransform as BinaryFile; + if (serializedTransform instanceof Blob) { + const serializedTransformBuffer = await serializedTransform.arrayBuffer(); + serializedTransformFile = { + path: serializedTransform.name, + data: new Uint8Array(serializedTransformBuffer), + }; + } + + let io = null; + if (mimeType && mimeToTransformIo.has(mimeType)) { + io = mimeToTransformIo.get(mimeType); + } else if (extensionToTransformIo.has(extension)) { + io = extensionToTransformIo.get(extension); + } else { + for (const readerWriter of transformIoIndex.values()) { + if (readerWriter[0] !== null) { + let { + webWorker: testWebWorker, + couldRead, + transform, + } = await (readerWriter[0] as unknown as Reader)( + { + path: serializedTransformFile.path, + data: serializedTransformFile.data.slice(), + }, + { + floatParameters: options.floatParameters, + webWorker: usedWebWorker, + noCopy: options?.noCopy, + } + ); + usedWebWorker = testWebWorker; + if (couldRead) { + return { webWorker: usedWebWorker, transform }; + } + } + } + } + if (!io) { + throw Error("Could not find IO for: " + fileName); + } + const readerWriter = transformIoIndex.get(io as string); + + const reader = (readerWriter as Array)[0]; + let { + webWorker: testWebWorker, + couldRead, + transform, + } = await reader(serializedTransformFile, { + floatParameters: options.floatParameters, + webWorker: usedWebWorker, + noCopy: options?.noCopy, + }); + usedWebWorker = testWebWorker; + if (!couldRead) { + throw Error("Could not read: " + fileName); + } + + return { webWorker: usedWebWorker, transform }; +} + +export default readTransform; diff --git a/packages/transform-io/typescript/src/transform-io-index.ts b/packages/transform-io/typescript/src/transform-io-index.ts new file mode 100644 index 000000000..80c9b3596 --- /dev/null +++ b/packages/transform-io/typescript/src/transform-io-index.ts @@ -0,0 +1,23 @@ +import hdf5ReadTransform from "./hdf5-read-transform.js"; +import hdf5WriteTransform from "./hdf5-write-transform.js"; +import matReadTransform from "./mat-read-transform.js"; +import matWriteTransform from "./mat-write-transform.js"; +import mncReadTransform from "./mnc-read-transform.js"; +import mncWriteTransform from "./mnc-write-transform.js"; +import txtReadTransform from "./txt-read-transform.js"; +import txtWriteTransform from "./txt-write-transform.js"; +import wasmReadTransform from "./wasm-read-transform.js"; +import wasmWriteTransform from "./wasm-write-transform.js"; +import wasmZstdReadTransform from "./wasm-zstd-read-transform.js"; +import wasmZstdWriteTransform from "./wasm-zstd-write-transform.js"; + +const transformIoIndex = new Map([ + ["hdf5", [hdf5ReadTransform, hdf5WriteTransform]], + ["mat", [matReadTransform, matWriteTransform]], + ["mnc", [mncReadTransform, mncWriteTransform]], + ["txt", [txtReadTransform, txtWriteTransform]], + ["wasm", [wasmReadTransform, wasmWriteTransform]], + ["wasmZstd", [wasmZstdReadTransform, wasmZstdWriteTransform]], +]); + +export default transformIoIndex; diff --git a/packages/transform-io/typescript/src/write-transform-node.ts b/packages/transform-io/typescript/src/write-transform-node.ts index 84b7c2509..d87a7f498 100644 --- a/packages/transform-io/typescript/src/write-transform-node.ts +++ b/packages/transform-io/typescript/src/write-transform-node.ts @@ -69,8 +69,6 @@ async function writeTransformNode( const readerWriter = transformIoIndexNode.get(io as string); const writer = (readerWriter as Array)[1]; - console.log("writer", writer); - console.log("inputTransform", inputTransform); let { couldWrite } = await writer(inputTransform, absoluteFilePath, { useCompression: options.useCompression, }); diff --git a/packages/transform-io/typescript/src/write-transform-result.ts b/packages/transform-io/typescript/src/write-transform-result.ts new file mode 100644 index 000000000..ca4efc71e --- /dev/null +++ b/packages/transform-io/typescript/src/write-transform-result.ts @@ -0,0 +1,11 @@ +import { BinaryFile } from "itk-wasm"; + +interface WriteTransformResult { + /** WebWorker used for computation */ + webWorker: Worker | null; + + /** Output image serialized in the file format. */ + serializedTransform: BinaryFile; +} + +export default WriteTransformResult; diff --git a/packages/transform-io/typescript/src/write-transform.ts b/packages/transform-io/typescript/src/write-transform.ts new file mode 100644 index 000000000..6638c0374 --- /dev/null +++ b/packages/transform-io/typescript/src/write-transform.ts @@ -0,0 +1,96 @@ +import { TransformList, BinaryFile, getFileExtension } from "itk-wasm"; + +import mimeToTransformIo from "./mime-to-transform-io.js"; +import extensionToTransformIo from "./extension-to-transform-io.js"; +import transformIoIndex from "./transform-io-index.js"; +import WriteTransformOptions from "./write-transform-options.js"; +import WriteTransformResult from "./write-transform-result.js"; + +interface WriterOptions { + floatParameters?: boolean; + useCompression?: boolean; + /** WebWorker for computation. Set to null to create a new worker. Or, pass an existing worker. Or, set to `false` to run in the current thread / worker. */ + webWorker?: Worker | null | boolean; +} +interface WriterResult { + webWorker: Worker; + couldWrite: boolean; + serializedTransform: BinaryFile; +} +type Writer = ( + transform: TransformList, + serializedTransform: string, + options: WriterOptions +) => Promise; + +/** + * Write an ITK-Wasm TransformList converted to an serialized transform file format + * + * @param {TransformList} transform - Input transform + * @param {string} serializedTransform - Output transform serialized in the file format. + * @param {WriteTransformOptions} options - options object + * + * @returns {Promise} - result object + */ +async function writeTransform( + transform: TransformList, + serializedTransform: string, + options: WriteTransformOptions = {} +): Promise { + let inputTransform = transform; + + const mimeType = options.mimeType; + const extension = getFileExtension(serializedTransform).toLowerCase(); + let usedWebWorker = options.webWorker; + + let io = null; + if (typeof mimeType !== "undefined" && mimeToTransformIo.has(mimeType)) { + io = mimeToTransformIo.get(mimeType); + } else if (extensionToTransformIo.has(extension)) { + io = extensionToTransformIo.get(extension); + } else { + for (const readerWriter of transformIoIndex.values()) { + if (readerWriter[1] !== null) { + let { + webWorker: testWebWorker, + couldWrite, + serializedTransform: serializedTransformBuffer, + } = await (readerWriter[1] as unknown as Writer)( + inputTransform, + serializedTransform, + options + ); + usedWebWorker = testWebWorker; + if (couldWrite) { + return { + webWorker: usedWebWorker as Worker, + serializedTransform: serializedTransformBuffer, + }; + } + } + } + } + if (!io) { + throw Error("Could not find IO for: " + serializedTransform); + } + const readerWriter = transformIoIndex.get(io as string); + + const writer = (readerWriter as Array)[1]; + let { + webWorker: testWebWorker, + couldWrite, + serializedTransform: serializedTransformBuffer, + } = await writer(inputTransform, serializedTransform, options); + usedWebWorker = testWebWorker; + if (!couldWrite) { + throw Error("Could not write: " + serializedTransform); + } + + const result = { + webWorker: usedWebWorker as Worker, + serializedTransform: serializedTransformBuffer, + }; + return result; +} + +export default writeTransform; diff --git a/packages/transform-io/typescript/vite.config.js b/packages/transform-io/typescript/vite.config.js index eb915742e..e07ca6410 100644 --- a/packages/transform-io/typescript/vite.config.js +++ b/packages/transform-io/typescript/vite.config.js @@ -1,30 +1,45 @@ -import { defineConfig } from 'vite' -import { viteStaticCopy } from 'vite-plugin-static-copy' -import path from 'path' +import { defineConfig } from "vite"; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import path from "path"; -const base = process.env.VITE_BASE_URL || '/' +const base = process.env.VITE_BASE_URL || "/"; export default defineConfig({ - root: path.join('test', 'browser', 'demo-app'), + root: path.join("test", "browser", "demo-app"), base, + server: { + port: 5181, + }, build: { - outDir: '../../../demo-app', + outDir: "../../../demo-app", emptyOutDir: true, }, worker: { - format: 'es' + format: "es", }, optimizeDeps: { - exclude: ['itk-wasm', '@itk-wasm/image-io', '@itk-wasm/mesh-io', '@thewtex/zstddec', '@itk-viewer/io'] + exclude: [ + "itk-wasm", + "@itk-wasm/image-io", + "@itk-wasm/mesh-io", + "@thewtex/zstddec", + "@itk-viewer/io", + ], }, plugins: [ // put lazy loaded JavaScript and Wasm bundles in dist directory viteStaticCopy({ targets: [ - { src: '../../../dist/pipelines/*', dest: 'pipelines' }, - { src: '../../../node_modules/@itk-wasm/image-io/dist/pipelines/*.{js,wasm,wasm.zst}', dest: 'pipelines' }, - { src: '../../../node_modules/@itk-wasm/mesh-io/dist/pipelines/*.{js,wasm,wasm.zst}', dest: 'pipelines' }, + { src: "../../../dist/pipelines/*", dest: "pipelines" }, + { + src: "../../../node_modules/@itk-wasm/image-io/dist/pipelines/*.{js,wasm,wasm.zst}", + dest: "pipelines", + }, + { + src: "../../../node_modules/@itk-wasm/mesh-io/dist/pipelines/*.{js,wasm,wasm.zst}", + dest: "pipelines", + }, ], - }) + }), ], -}) +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d35c7ec9..7d4f3ce3d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -787,12 +787,18 @@ importers: ava: specifier: ^6.1.3 version: 6.1.3 + cypress: + specifier: ^13.11.0 + version: 13.11.0 esbuild: specifier: ^0.19.8 version: 0.19.12 shx: specifier: ^0.3.4 version: 0.3.4 + start-server-and-test: + specifier: ^2.0.4 + version: 2.0.4 typescript: specifier: ^5.3.2 version: 5.4.5