Skip to content

Commit

Permalink
WIP: feat(transform-io): add readTransform, writeTransform
Browse files Browse the repository at this point in the history
And support code.
  • Loading branch information
thewtex committed Nov 12, 2024
1 parent c41150f commit a34f0e5
Show file tree
Hide file tree
Showing 28 changed files with 728 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import snakeCase from "../snake-case.js"
import snakeCase from '../snake-case.js'

function inputParametersPython(functionName, indent, parameter, required) {
let initResult = ''
Expand Down Expand Up @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import snakeCase from "../snake-case.js"
import snakeCase from '../snake-case.js'

function outputPython(functionName, prefix, indent, parameter) {
const parameterName = snakeCase(parameter.name)
let initResult = ''
let methodResult = ''
let runResult = ''

switch(parameter.type) {
switch (parameter.type) {
// case 'OUTPUT_TEXT_FILE:FILE':
// case 'OUTPUT_TEXT_STREAM':
// result += `${indent}<sl-textarea disabled name="${parameter.name}" label="${snakeCase(parameter.name)}" help-text="${parameter.description}"></sl-textarea>\n`
// result += `${indent}<sl-button variant="neutral" name="${parameter.name}-download">${snakeCase(parameter.name)}</sl-button>\n`
// result += `<br /><br />\n`
// break
// result += `${indent}<sl-textarea disabled name="${parameter.name}" label="${snakeCase(parameter.name)}" help-text="${parameter.description}"></sl-textarea>\n`
// result += `${indent}<sl-button variant="neutral" name="${parameter.name}-download">${snakeCase(parameter.name)}</sl-button>\n`
// result += `<br /><br />\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`
Expand Down Expand Up @@ -53,7 +53,9 @@ function outputPython(functionName, prefix, indent, parameter) {
// result += `<br /><br />\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 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function inputParametersDemoHtml(
case 'INPUT_JSON':
case 'INPUT_MESH':
case 'INPUT_POINT_SET':
case 'INPUT_TRANSFORM':
result += `${prefix}${indent}<label for="${parameter.name}-file"><sl-button name="${parameter.name}-file-button" variant="primary" outline onclick="this.parentElement.nextElementSibling.click()">Upload</sp-button></label><input type="file" name="${parameter.name}-file" style="display: none"/>\n`
result += `${prefix}${indent}<sl-tooltip ${tooltipContent}><sl-details id="${functionName}-${parameter.name}-details" summary="${label}: ${description}" disabled></sl-details></sl-tooltip>\n`
result += '<br /><br />\n'
Expand All @@ -76,8 +77,11 @@ function inputParametersDemoHtml(
result += `${prefix}${indent}<sl-tooltip ${tooltipContent}><itk-image-details id="${functionName}-${parameter.name}-details" summary="${label}: ${description}" disabled></itk-image-details></sl-tooltip>\n`
result += '<br /><br />\n'
break
break
default:
console.error(`Unexpected interface type: ${parameterType}`)
console.error(
`inputParametersDemoHtml: Unexpected interface type: ${parameterType}`
)
process.exit(1)
}
return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -172,12 +173,29 @@ function inputParametersDemoTypeScript(
result += `${indent}${indent}const details = document.getElementById("${functionName}-${parameter.name}-details")\n`
result += `${indent}${indent}details.innerHTML = \`<pre>$\{globalThis.escapeHtml(JSON.stringify(pointSet, globalThis.interfaceTypeJsonReplacer, 2))}</pre>\`\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 = \`<pre>$\{globalThis.escapeHtml(JSON.stringify(inputTransform, globalThis.interfaceTypeJsonReplacer, 2))}</pre>\`\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 = \`<pre>$\{globalThis.escapeHtml(JSON.stringify(transform, globalThis.interfaceTypeJsonReplacer, 2))}</pre>\`\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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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`
Expand All @@ -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`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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(
Expand All @@ -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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,24 @@ function outputDemoHtml(functionName, prefix, indent, parameter) {
result += `<br /><br />\n`
}
break
case 'OUTPUT_TRANSFORM':
{
result += `${prefix}${indent}<sl-details disabled id="${functionName}-${parameter.name}-details" summary="${camelCase(parameter.name)}: ${description}"></sl-details>\n`

result += `${prefix}${indent}<sl-select id="${functionName}-${parameter.name}-output-format" placeholder="Format">\n`
const formats = ['h5', 'txt', 'mat', 'xfm']
formats.forEach((format) => {
result += `${prefix}${indent}${indent}<sl-option value="${format}">${format}</sl-option>\n`
})
result += `${prefix}${indent}</sl-select>\n`
result += `${prefix}${indent}<sl-button variant="neutral" outline name="${parameter.name}-download" disabled>Download</sl-button>\n`
result += `<br /><br />\n`
}
break
default:
console.error(`Unexpected interface type: ${parameterType}`)
console.error(
`outputDemoHtml: Unexpected interface type: ${parameterType}`
)
process.exit(1)
}
return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/transform-io/typescript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cypress/screenshots/
9 changes: 9 additions & 0 deletions packages/transform-io/typescript/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "cypress";

export default defineConfig({
e2e: {
defaultCommandTimeout: 40000,
setupNodeEvents(on, config) {
},
},
});
24 changes: 24 additions & 0 deletions packages/transform-io/typescript/cypress/e2e/common.ts
Original file line number Diff line number Diff line change
@@ -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,
])
);
}
72 changes: 72 additions & 0 deletions packages/transform-io/typescript/cypress/e2e/read-transform.ty.ts
Original file line number Diff line number Diff line change
@@ -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')
}
})
})
})
Loading

0 comments on commit a34f0e5

Please sign in to comment.