Skip to content

Commit

Permalink
feat(mesh-io): read-mesh and write-mesh
Browse files Browse the repository at this point in the history
  • Loading branch information
thewtex committed Nov 28, 2023
1 parent 9393607 commit 4289117
Show file tree
Hide file tree
Showing 25 changed files with 2,116 additions and 24 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"build:testData": "npm run build:testData:dicom && npm run build:testData:image-io",
"build:testData:dicom": "dam download packages/dicom/test/data packages/dicom/test/data.tar.gz bafybeicskxufnvuem6342pkfwgeo3siiozgzmfo5f34woge6aptuzuwzzu https://github.com/InsightSoftwareConsortium/itk-wasm/releases/download/itk-wasm-v1.0.0-b.119/dicom-test-data.tar.gz https://w3s.link/ipfs/bafybeiby67winzvozowf4moqthwunuxxscssitnb6wahxv4ugvfxhu2vki/data.tar.gz",
"build:testData:image-io": "dam download packages/image-io/test/data packages/image-io/test/data.tar.gz bafybeibyjhkcrinl2lotw5g2vngjs23aaenv3tjzxssm35jxaci5ylsqia https://github.com/InsightSoftwareConsortium/itk-wasm/releases/download/itk-wasm-v1.0.0-b.155/image-io-test-data.tar.gz https://w3s.link/ipfs/bafybeiewe4rankwwe7nw7qm2g3qclflhci2e53wthn3gukdlhbff64pua4/data.tar.gz",
"build:testData:mesh-io": "dam download packages/mesh-io/test/data packages/mesh-io/test/data.tar.gz bafybeibyjhkcrinl2lotw5g2vngjs23aaenv3tjzxssm35jxaci5ylsqia https://github.com/InsightSoftwareConsortium/itk-wasm/releases/download/itk-wasm-v1.0.0-b.155/mesh-io-test-data.tar.gz https://w3s.link/ipfs/bafybeiewe4rankwwe7nw7qm2g3qclflhci2e53wthn3gukdlhbff64pua4/data.tar.gz",
"build:testData:mesh-io": "dam download packages/mesh-io/test/data packages/mesh-io/test/data.tar.gz bafkreian7qpq5byeiyc5ruaa4sejffmtqvttncybsxs3nlm4injzxxflp4 https://github.com/InsightSoftwareConsortium/itk-wasm/releases/download/itk-wasm-v1.0.0-b.155/mesh-io-test-data.tar.gz",
"build:debug": "npm run build:emscripten -- --debug && npm run build:tsc && npm run build:tscWorkersModuleLoader && npm run build:tscWebWorkers && npm run build:workerBundle && npm run build:workerMinBundle && npm run build:webpack -- --mode development",
"build:tsc": "tsc --pretty",
"build:tscWorkersModuleLoader": "tsc --types --lib es2017,webworker --rootDir ./src/ --outDir ./dist/ --moduleResolution node --target es2017 --module es2020 --strict --forceConsistentCasingInFileNames --declaration ./src/core/internal/loadEmscriptenModuleWebWorker.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/image-io/typescript/src/read-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface ReaderOptions {
/** Only read image metadata -- do not read pixel data. */
informationOnly?: boolean
}
type Reader = (webWorker: null | Worker, serializedImage: File | BinaryFile, options: ReaderOptions) => Promise<ReaderResult>
type Reader = (webWorker: null | Worker | boolean, serializedImage: File | BinaryFile, options: ReaderOptions) => Promise<ReaderResult>

/**
* Read an image file format and convert it to the itk-wasm file format
Expand All @@ -33,7 +33,7 @@ type Reader = (webWorker: null | Worker, serializedImage: File | BinaryFile, opt
* @returns {Promise<ReadImageResult>} - result object with the image and the web worker used
*/
async function readImage(
webWorker: null | Worker,
webWorker: null | Worker | boolean,
serializedImage: File | BinaryFile,
options: ReadImageOptions = {}
) : Promise<ReadImageResult> {
Expand Down
4 changes: 2 additions & 2 deletions packages/image-io/typescript/src/write-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface WriterResult {
couldWrite: boolean
serializedImage: BinaryFile
}
type Writer = (webWorker: Worker | null, image: Image, serializedImage: string, options: WriterOptions) => Promise<WriterResult>
type Writer = (webWorker: Worker | null | boolean, image: Image, serializedImage: string, options: WriterOptions) => Promise<WriterResult>

/**
* Write an itk-wasm Image converted to an serialized image file format
Expand All @@ -33,7 +33,7 @@ type Writer = (webWorker: Worker | null, image: Image, serializedImage: string,
* @returns {Promise<WriteImageResult>} - result object
*/
async function writeImage(
webWorker: null | Worker,
webWorker: null | Worker | boolean,
image: Image,
serializedImage: string,
options: WriteImageOptions = {}
Expand Down
66 changes: 66 additions & 0 deletions packages/mesh-io/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Import:

```js
import {
readMesh,
writeMesh,
byuReadMesh,
byuWriteMesh,
freeSurferAsciiReadMesh,
Expand All @@ -43,6 +45,70 @@ import {
} from "@itk-wasm/mesh-io"
```

#### readMesh

*Read a mesh file format and convert it to the itk-wasm file format*

```ts
async function readMesh(
webWorker: null | Worker | boolean,
serializedMesh: File | BinaryFile,
options: ReadMeshOptions = {}
) : Promise<ReadMeshResult>
```

| Parameter | Type | Description |
| :--------------: | :-------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `webWorker` | *null or Worker or boolean* | WebWorker to use 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. |
| `serializedMesh` | *File | BinaryFile* | Input mesh serialized in the file format |

**`ReadMeshOptions` interface:**

| Property | Type | Description |
| :---------------: | :-------: | :-------------------------------------------------- |
| `informationOnly` | *boolean* | Only read image metadata -- do not read pixel data. |

**`ReadMeshResult` interface:**

| Property | Type | Description |
| :---------: | :--------------: | :----------------------------------------------------------------------- |
| `webWorker` | *Worker* | WebWorker used for computation. |
| `mesh` | *Mesh* | Output mesh |

#### writeMesh

*Write an itk-wasm file format converted to an mesh file format*

```ts
async function writeMesh(
webWorker: null | Worker | boolean,
mesh: Mesh,
serializedMesh: string,
options: WriteMeshOptions = {}
) : Promise<WriteMeshResult>
```

| Parameter | Type | Description |
| :--------------: | :-------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `webWorker` | *null or Worker or boolean* | WebWorker to use 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. |
| `mesh` | *Mesh* | Input mesh |
| `serializedMesh` | *string* | Output mesh |

**`WriteMeshOptions` interface:**

| Property | Type | Description |
| :---------------: | :-------: | :------------------------------------------------------- |
| `informationOnly` | *boolean* | Only write image metadata -- do not write pixel data. |
| `useCompression` | *boolean* | Use compression in the written file, if supported |
| `binaryFileType` | *boolean* | Use a binary file type in the written file, if supported |

**`WriteMeshResult` interface:**

| Property | Type | Description |
| :--------------: | :--------------: | :-------------------------------------------------------------------------- |
| `webWorker` | *Worker* | WebWorker used for computation. |
| `serializedMesh` | *BinaryFile* | Output mesh |

#### byuReadMesh

*Read a mesh file format and convert it to the itk-wasm file format*
Expand Down
9 changes: 9 additions & 0 deletions packages/mesh-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) {
},
},
});
13 changes: 13 additions & 0 deletions packages/mesh-io/typescript/cypress/e2e/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const demoServer = 'http://localhost:5173'

import { IntTypes, FloatTypes, PixelTypes } from 'itk-wasm'

export function verifyMesh (mesh) {
cy.expect(mesh.meshType.dimension).to.equal(3)
cy.expect(mesh.meshType.pointComponentType).to.equal(FloatTypes.Float32)
cy.expect(mesh.meshType.cellComponentType).to.equal(IntTypes.UInt32)
cy.expect(mesh.meshType.pointPixelType).to.equal(PixelTypes.Scalar)
cy.expect(mesh.meshType.cellPixelType).to.equal(PixelTypes.Scalar)
cy.expect(mesh.numberOfPoints).to.equal(2903)
cy.expect(mesh.numberOfCells).to.equal(3263)
}
72 changes: 72 additions & 0 deletions packages/mesh-io/typescript/cypress/e2e/read-mesh.cy.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(null, { 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(null, 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(null, cowFile)
const { mesh } = await win.meshIo.readMesh(webWorker, cowFile)
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(null, invalidFile)
webWorker.terminate()
} catch (error) {
cy.expect(error.message).to.equal('Could not find IO for: invalid.file')
}
})
})
})
40 changes: 40 additions & 0 deletions packages/mesh-io/typescript/cypress/e2e/write-mesh.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { demoServer, verifyMesh } from './common.ts'

describe('write-mesh', () => {
beforeEach(function() {
cy.visit(demoServer)

const testPathPrefix = '../test/data/input/'

const testImageFiles = [
'cow.iwm.cbor'
]
testImageFiles.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(null, { data: new Uint8Array(arrayBuffer), path: 'cow.iwm.cbor' })
const { serializedMesh } = await win.meshIo.writeMesh(webWorker, mesh, 'cow.vtk')
const { mesh: meshBack } = await win.meshIo.readMesh(webWorker, serializedMesh)
webWorker.terminate()
verifyMesh(meshBack)
})
})
})
37 changes: 37 additions & 0 deletions packages/mesh-io/typescript/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// 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<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
31 changes: 31 additions & 0 deletions packages/mesh-io/typescript/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -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
})

12 changes: 12 additions & 0 deletions packages/mesh-io/typescript/cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"include": [
"**/*.ts"
],
"compilerOptions": {
"noEmit": false,
"sourceMap": false,
"inlineSourceMap": true,
"types": ["cypress"]
},
}
11 changes: 10 additions & 1 deletion packages/mesh-io/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@
},
"scripts": {
"start": "npm run copyShoelaceAssets && vite",
"test": "echo \"Error: no test specified\" && exit 1",
"test": "npm run test:node && npm run test:browser",
"test:node": "ava",
"test:browser": "npm run test:browser:chrome && npm run test:browser:firefox",
"test:browser:firefox": "start-server-and-test start http-get://localhost:5173 cypress:runFirefox",
"test:browser:chrome": "start-server-and-test start http-get://localhost:5173 cypress:runChrome",
"test:browser:debug": "start-server-and-test start http-get://localhost:5173 cypress:open",
"cypress:open": "npx cypress open",
"cypress:runChrome": "npx cypress run --browser chrome",
"cypress:runFirefox": "npx cypress run --browser firefox",
"build": "npm run build:tsc && npm run build:browser:workerEmbedded && npm run build:browser:workerEmbeddedMin && npm run 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",
Expand All @@ -41,8 +48,10 @@
"@types/mime-types": "^2.1.4",
"@types/node": "^20.2.5",
"ava": "^5.3.1",
"cypress": "^13.6.0",
"esbuild": "^0.19.5",
"shx": "^0.3.4",
"start-server-and-test": "^2.0.3",
"typescript": "^5.0.4",
"vite": "^4.5.0",
"vite-plugin-static-copy": "^0.17.0"
Expand Down
Loading

0 comments on commit 4289117

Please sign in to comment.