Skip to content

Commit

Permalink
web?
Browse files Browse the repository at this point in the history
  • Loading branch information
anibalsolon committed Mar 16, 2022
1 parent ec9df9f commit fa99026
Show file tree
Hide file tree
Showing 28 changed files with 3,752 additions and 437 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.vscode-test/
.vscode-test-web/
*.vsix
dist
/data
Expand Down
4 changes: 1 addition & 3 deletions .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@
"exit": true,
"require": ["ts-node/register", "source-map-support/register"],
"extension": ["ts"],
"exclude": [
"test/suite/**"
]
"spec": "./test/unit/**"
}
10 changes: 10 additions & 0 deletions extension-web/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as vscode from 'vscode';
import { NiftiEditorProvider } from '../extension/provider';

export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(NiftiEditorProvider.register(context));
}

export function deactivate() {
return;
}
19 changes: 11 additions & 8 deletions extension/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ import * as fs from 'fs';
import * as vscode from 'vscode';
import { v4 } from 'uuid';
import { Disposable } from './dispose';
import { Normalizer, Bufferizer } from './utils';
import { Normalizer, FileReference } from './fs-utils';
import { Nifti, NiftiFactory } from './formats/nifti';
import { Readable } from 'stream';

export class NiftiDocument extends Disposable implements vscode.CustomDocument {
private readonly _uri: vscode.Uri;
private readonly _uuid: string;
private readonly _fd: number;
private readonly _fd: FileReference;
private _doc?: Nifti;

constructor(
uri: vscode.Uri
uri: vscode.Uri,
fd?: FileReference,
) {
super();
this._uri = uri;
this._uuid = v4();
this._fd = fs.openSync(uri.path, 'r');
this._fd = fd || fs.openSync(uri.path, 'r');
}

public get uri() { return this._uri; }
Expand All @@ -29,15 +31,14 @@ export class NiftiDocument extends Disposable implements vscode.CustomDocument {
}
return this._doc.header();
}
public async data(offset?: number, volumes?: number): Promise<NodeJS.ReadableStream> {
public async data(offset?: number, volumes?: number): Promise<Readable> {
if (!this._doc) {
this._doc = await NiftiFactory.build(this._fd);
}
const { values: { min, max } } = await this._doc.header();
const stream = await this._doc.values(offset, volumes);
return stream
.pipe(new Normalizer(min, max, 0, 32767))
.pipe(new Bufferizer());
.pipe(new Normalizer(min, max, 0, 32767));
}

private readonly _onDidDispose = this._register(new vscode.EventEmitter<void>());
Expand All @@ -46,7 +47,9 @@ export class NiftiDocument extends Disposable implements vscode.CustomDocument {
dispose(): void {
this._onDidDispose.fire();
super.dispose();
fs.closeSync(this._fd);
if (typeof this._fd === 'number') {
fs.closeSync(this._fd);
}
}

async save(): Promise<void> {
Expand Down
5 changes: 4 additions & 1 deletion extension/fileserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AddressInfo } from 'net';
import * as http from 'http';
import * as vscode from 'vscode';
import { NiftiDocument } from './document';
import { Bufferizer } from './fs-utils';

export class FileServer {

Expand Down Expand Up @@ -37,7 +38,9 @@ export class FileServer {

const data = await this._documents[url].data(0, 1);
res.writeHead(200);
data.pipe(res, { end: false });
data
.pipe(new Bufferizer())
.pipe(res, { end: false });
await new Promise<void>((resolve, reject) => {
data.once('end', () => {
res.end();
Expand Down
100 changes: 77 additions & 23 deletions extension/formats/nifti/base.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import zlib from 'zlib';
import { Readable } from 'stream';
import * as zlib from 'zlib';
import { Slicer, Stepper, Caster, createReadableStream, awaitReadableStream, isGzipped } from '../../utils';
import { Buffer } from 'buffer';
import { FileReference, Slicer, Stepper, Caster, createReadableStream, awaitReadableStream, isGzipped } from '../../fs-utils';

export enum NiftiDataType {
NONE = 'NONE',
Expand Down Expand Up @@ -100,9 +101,56 @@ export interface NiftiHeader {
qOffset: { x: number, y: number, z: number },
}

function getFirstAndLast(
buffer: Buffer,
offset: number,
): { first: number; last: number } {
const first = buffer[offset] as number | undefined;
const last = buffer[offset + 7] as number | undefined;
return { first: first || 0, last: last || 0 };
}

function readBigInt64BE(buffer: Buffer, offset = 0): bigint {
const { first, last } = getFirstAndLast(buffer, offset);

const val =
(first << 24) + // Overflow
buffer[++offset] * 2 ** 16 +
buffer[++offset] * 2 ** 8 +
buffer[++offset];
return (
(BigInt(val) << BigInt(32)) +
BigInt(
buffer[++offset] * 2 ** 24 +
buffer[++offset] * 2 ** 16 +
buffer[++offset] * 2 ** 8 +
last,
)
);
}

function readBigInt64LE(buffer: Buffer, offset = 0): bigint {
const { first, last } = getFirstAndLast(buffer, offset);

const val =
buffer[offset + 4] +
buffer[offset + 5] * 2 ** 8 +
buffer[offset + 6] * 2 ** 16 +
(last << 24); // Overflow
return (
(BigInt(val) << BigInt(32)) +
BigInt(
first +
buffer[++offset] * 2 ** 8 +
buffer[++offset] * 2 ** 16 +
buffer[++offset] * 2 ** 24,
)
);
}

export abstract class Nifti {

protected readonly _fd: number;
protected readonly _fd: FileReference;
protected readonly _gzipped: boolean;

protected _endianness: 'BE' | 'LE' = 'BE';
Expand All @@ -111,18 +159,18 @@ export abstract class Nifti {
protected static HEADER_SIZE = 348;
protected static MAGIC_COOKIE = 348;

constructor (fd: number) {
constructor (fd: FileReference) {
this._fd = fd;
this._gzipped = isGzipped(fd);
}

public static async readStream(fd: number, offset: number, length?: number, steps?: number, gzipped?: boolean): Promise<NodeJS.ReadableStream> {
public static async readStream(fd: FileReference, offset: number, length?: number, steps?: number, gzipped?: boolean): Promise<Readable> {
if (gzipped === undefined) {
gzipped = isGzipped(fd);
}
const stream = await createReadableStream('', { fd, start: 0, autoClose: false });
const stream = await createReadableStream(fd, { start: 0, autoClose: false });
const slicer = new Slicer(offset, length);
let pipe: NodeJS.ReadableStream;
let pipe: Readable;
if (gzipped) {
const decompressStream = zlib.createGunzip();
pipe = stream.pipe(decompressStream).pipe(slicer);
Expand All @@ -136,22 +184,26 @@ export abstract class Nifti {
return pipe;
}

private async readStream(offset: number, length?: number, steps?: number, gzipped?: boolean): Promise<NodeJS.ReadableStream> {
private async readStream(offset: number, length?: number, steps?: number, gzipped?: boolean): Promise<Readable> {
return Nifti.readStream(this._fd, offset, length, steps, gzipped);
}

static read(buffer: Buffer, type: NiftiDataTypeNumber, offset: number, endianness: 'BE' | 'LE'): number;
static read(buffer: Buffer, type: NiftiDataTypeBigInt, offset: number, endianness: 'BE' | 'LE'): bigint;
static read(buffer: Buffer, type: NiftiDataType, offset: number, endianness: 'BE' | 'LE'): bigint | number {

const readBigInt64BEFn = buffer.readBigInt64BE ? buffer.readBigInt64BE : (offset: number) => readBigInt64BE(buffer, offset);
const readBigInt64LEFn = buffer.readBigInt64LE ? buffer.readBigInt64LE : (offset: number) => readBigInt64LE(buffer, offset);

switch (type) {
case NiftiDataType.FLOAT64: return endianness === 'BE' ? buffer.readDoubleBE(offset) : buffer.readDoubleLE(offset);
case NiftiDataType.FLOAT32: return endianness === 'BE' ? buffer.readFloatBE(offset) : buffer.readFloatLE(offset);
case NiftiDataType.INT16: return endianness === 'BE' ? buffer.readInt16BE(offset) : buffer.readInt16LE(offset);
case NiftiDataType.INT32: return endianness === 'BE' ? buffer.readInt32BE(offset) : buffer.readInt32LE(offset);
case NiftiDataType.INT64: return endianness === 'BE' ? buffer.readBigInt64BE(offset) : buffer.readBigInt64LE(offset);
case NiftiDataType.INT64: return endianness === 'BE' ? readBigInt64BEFn(offset) : readBigInt64LEFn(offset);
case NiftiDataType.UINT16: return endianness === 'BE' ? buffer.readUInt16BE(offset) : buffer.readUInt16LE(offset);
case NiftiDataType.UINT32: return endianness === 'BE' ? buffer.readUInt32BE(offset) : buffer.readUInt32LE(offset);
case NiftiDataType.UINT64: return endianness === 'BE' ? buffer.readBigInt64BE(offset) : buffer.readBigInt64LE(offset);
case NiftiDataType.UINT64: return endianness === 'BE' ? readBigInt64BEFn(offset) : readBigInt64LEFn(offset);
case NiftiDataType.INT8: return buffer.readInt8(offset);
case NiftiDataType.UINT8: return buffer.readUInt8(offset);
}
Expand Down Expand Up @@ -236,8 +288,10 @@ export abstract class Nifti {
const _min = Math.min;
const _max = Math.max;
for await (const chunk of values) {
vmin = _min(vmin, _min(...chunk));
vmax = _max(vmax, _max(...chunk));
for (const value of chunk) {
vmin = _min(vmin, value);
vmax = _max(vmax, value);
}
}
this._header.values.min = vmin;
this._header.values.max = vmax;
Expand All @@ -249,12 +303,12 @@ export abstract class Nifti {
return this._header;
}

async data(volumeOffset = 0, volumes?: number): Promise<NodeJS.ReadableStream> {
async data(volumeOffset = 0, volumes?: number): Promise<Readable> {
const { dataType, dataOffset, dataBits, dimensions } = await this.header();
const readingStep = NiftiDataType.bytes(dataType);
const volumeSize = dimensions[0] * dimensions[1] * dimensions[2] * (dataBits / 8);

const offset = dataOffset + volumeSize * volumeOffset;

const stream = await this.readStream(
offset,
volumes !== undefined ? volumeSize * volumes : undefined,
Expand All @@ -266,26 +320,26 @@ export abstract class Nifti {
async values(volumeOffset = 0, volumes?: number): Promise<Readable> {
const { dataType, endianness, values: { scaling: { slope, intercept } } } = await this.header();
const stream = await this.data(volumeOffset, volumes);
return stream.pipe(new Caster(dataType, endianness, {
scaling: { slope, intercept },
}));
return stream
.pipe(new Caster(dataType, endianness, {
scaling: { slope, intercept },
}));
}

static async version(fd: number): Promise<1 | 2> {
static async version(fd: FileReference): Promise<1 | 2> {
const N1_MAGIC_NUMBER_LOCATION = 344;
const N1_MAGIC_NUMBER = [0x6E, 0x2B, 0x31];

const stream = await Nifti.readStream(fd, 0, Nifti.HEADER_SIZE);
const buffer = <Buffer> stream.read(Nifti.HEADER_SIZE);

if (buffer === null) {
throw Error('Invalid file format');
}

if (
(Nifti.read(buffer, NiftiDataType.UINT8, N1_MAGIC_NUMBER_LOCATION, 'LE') === N1_MAGIC_NUMBER[0]) &&
(Nifti.read(buffer, NiftiDataType.UINT8, N1_MAGIC_NUMBER_LOCATION + 1, 'LE') === N1_MAGIC_NUMBER[1]) &&
(Nifti.read(buffer, NiftiDataType.UINT8, N1_MAGIC_NUMBER_LOCATION + 2, 'LE') === N1_MAGIC_NUMBER[2])
(Nifti.read(buffer, NiftiDataType.UINT8, N1_MAGIC_NUMBER_LOCATION + 1, 'LE') === N1_MAGIC_NUMBER[1]) &&
(Nifti.read(buffer, NiftiDataType.UINT8, N1_MAGIC_NUMBER_LOCATION + 2, 'LE') === N1_MAGIC_NUMBER[2])
) {
return 1;
}
Expand All @@ -295,8 +349,8 @@ export abstract class Nifti {

if (
(Nifti.read(buffer, NiftiDataType.UINT8, N2_MAGIC_NUMBER_LOCATION, 'LE') === N2_MAGIC_NUMBER[0]) &&
(Nifti.read(buffer, NiftiDataType.UINT8, N2_MAGIC_NUMBER_LOCATION + 1, 'LE') === N2_MAGIC_NUMBER[1]) &&
(Nifti.read(buffer, NiftiDataType.UINT8, N2_MAGIC_NUMBER_LOCATION + 2, 'LE') === N2_MAGIC_NUMBER[2])
(Nifti.read(buffer, NiftiDataType.UINT8, N2_MAGIC_NUMBER_LOCATION + 1, 'LE') === N2_MAGIC_NUMBER[1]) &&
(Nifti.read(buffer, NiftiDataType.UINT8, N2_MAGIC_NUMBER_LOCATION + 2, 'LE') === N2_MAGIC_NUMBER[2])
) {
return 2;
}
Expand Down
5 changes: 4 additions & 1 deletion extension/formats/nifti/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { Nifti } from './base';
import { Nifti1 } from './nifti1';
import { Nifti2 } from './nifti2';

// TODO move this to another place
import { FileReference } from '../../fs-utils';

export class NiftiFactory {
static async build(fd: number): Promise<Nifti> {
static async build(fd: FileReference): Promise<Nifti> {
const v: 1 | 2 = await Nifti.version(fd);
switch (v) {
case 1:
Expand Down
3 changes: 2 additions & 1 deletion extension/formats/nifti/nifti1.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'buffer';
import { Nifti, NiftiDataType } from './base';

export class Nifti1 extends Nifti {
Expand Down Expand Up @@ -79,4 +80,4 @@ export class Nifti1 extends Nifti {
scalingSlope, scalingIntercept
};
}
}
}
1 change: 1 addition & 0 deletions extension/formats/nifti/nifti2.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'buffer';
import { Nifti, NiftiDataType } from './base';

export class Nifti2 extends Nifti {
Expand Down
Loading

0 comments on commit fa99026

Please sign in to comment.