diff --git a/docs/source/channel_map.rst b/docs/source/channel_map.rst
index cd426f2..6f13ca8 100644
--- a/docs/source/channel_map.rst
+++ b/docs/source/channel_map.rst
@@ -30,10 +30,15 @@ Channel Map
User -> Frontend: Request channel map
activate Frontend
- Frontend -> Backend : 5. SET_IMAGE_CHANNELS (channel_map_enabled)
- activate Backend
- Frontend <--[#red] Backend : 6. RASTER_TILE_DATA [Check 1] (per channel)
- deactivate Backend
+
+ loop One request per channel
+ Frontend -> Backend : 5. SET_IMAGE_CHANNELS (channel_map_enabled)
+ activate Backend
+ Frontend <--[#red] Backend : 6. RASTER_TILE_DATA [Check 3]
+ Frontend <--[#red] Backend : 7. CHANNEL_MAP_FLOW_CONTROL [Check 4]
+ deactivate Backend
+ end
+
User <-- Frontend: Displays channel map
deactivate Frontend
@@ -44,6 +49,16 @@ See the `source code {
const msgController = MessageController.Instance;
+ const rasterTileDataLen = tiles.length; // one RasterTileData per tile
+ const rasterTileMsgLen = rasterTileDataLen + 2; // + RasterTileSync start & end
+
describe(`Register a session`, () => {
beforeAll(async () => {
await msgController.connect(testServerUrl);
@@ -302,6 +135,8 @@ describe('CHANNEL_MAP: Test loading multiple images and generating their channel
describe(`Go to "${assertItem.fileList.directory}" folder`, () => {
assertItem.fileOpenGroup.map((input, index) => {
+ const fileId = assertItem.fileOpenAckGroup[index].fileId as number;
+
describe(`Open ${input.file}`, () => {
let OpenFileAck: any;
test(
@@ -358,230 +193,84 @@ describe('CHANNEL_MAP: Test loading multiple images and generating their channel
});
});
- describe(`1) Set image channel map for the file "${assertItem.fileOpenGroup[index].file}"`, () => {
- let RasterTileMsgTemp: any;
- const rasterTileDataLen = assertItem.setImageChannelGroup[index].requiredTiles.tiles.length;
- const rasterTileMsgLen = rasterTileDataLen + 2;
-
- test(
- `Three RASTER_TILE_DATA should arrive within ${readFileTimeout} ms`,
- async () => {
- msgController.setChannels(assertItem.setImageChannelGroup[index]);
- const maxChan = assertItem.setImageChannelGroup[index].channelRange.max;
- const minChan = assertItem.setImageChannelGroup[index].channelRange.min;
- const channels = maxChan - minChan + 1;
- RasterTileMsgTemp = await ChannelMapStream(rasterTileDataLen, channels);
- },
- readFileTimeout
- );
-
- // Check file Ids
- test(`1st RASTER_TILE_DATA.file_id = ${assertItem.rasterTileDataGroup[index].fileId}`, () => {
- expect(RasterTileMsgTemp[1].fileId).toEqual(assertItem.rasterTileDataGroup[index].fileId);
- });
- test(`2nd RASTER_TILE_DATA.file_id = ${assertItem.rasterTileDataGroup[index].fileId}`, () => {
- expect(RasterTileMsgTemp[rasterTileMsgLen + 1].fileId).toEqual(
- assertItem.rasterTileDataGroup[index].fileId
- );
- });
- test(`3rd RASTER_TILE_DATA.file_id = ${assertItem.rasterTileDataGroup[index].fileId}`, () => {
- expect(RasterTileMsgTemp[2 * rasterTileMsgLen + 1].fileId).toEqual(
- assertItem.rasterTileDataGroup[index].fileId
- );
- });
-
- // Check channels
- test(`1st RASTER_TILE_DATA.channel = ${assertItem.rasterTileDataGroup[index].channel + 1}`, () => {
- expect(RasterTileMsgTemp[1].channel).toEqual(assertItem.rasterTileDataGroup[index].channel + 1);
- });
- test(`2nd RASTER_TILE_DATA.channel = ${assertItem.rasterTileDataGroup[index].channel + 2}`, () => {
- expect(RasterTileMsgTemp[rasterTileMsgLen + 1].channel).toEqual(
- assertItem.rasterTileDataGroup[index].channel + 2
+ // Render the channel map view one channel at a time. Each request is answered by
+ // that channel's raster tiles followed by a CHANNEL_MAP_FLOW_CONTROL ack; the next
+ // request is only sent after the previous channel completes.
+ const channelMapSequence = [...assertItem.channelMapView, assertItem.channelMapJump];
+
+ channelMapSequence.forEach((channel, seq) => {
+ const label =
+ seq < assertItem.channelMapView.length
+ ? `view channel ${channel}`
+ : `jump to channel ${channel}`;
+
+ describe(`Channel map (${label}) for the file "${assertItem.fileOpenGroup[index].file}"`, () => {
+ let response: { rasterTileMsgs: any[]; flowControl: any };
+
+ test(
+ `RASTER_TILE_DATA and CHANNEL_MAP_FLOW_CONTROL should arrive within ${readFileTimeout} ms`,
+ async () => {
+ msgController.setChannels(channelMapRequest(fileId, channel));
+ response = await ChannelMapStream(rasterTileDataLen);
+ // one channel = # tiles + 2 RasterTileSync
+ expect(response.rasterTileMsgs.length).toEqual(rasterTileMsgLen);
+ },
+ readFileTimeout
);
- });
- test(`3rd RASTER_TILE_DATA.channel = ${assertItem.rasterTileDataGroup[index].channel + 3}`, () => {
- expect(RasterTileMsgTemp[2 * rasterTileMsgLen + 1].channel).toEqual(
- assertItem.rasterTileDataGroup[index].channel + 3
- );
- });
-
- // Check stokes
- test(`1st RASTER_TILE_DATA.stokes = ${assertItem.rasterTileDataGroup[index].stokes}`, () => {
- expect(RasterTileMsgTemp[1].stokes).toEqual(assertItem.rasterTileDataGroup[index].stokes);
- });
- test(`2nd RASTER_TILE_DATA.stokes = ${assertItem.rasterTileDataGroup[index].stokes}`, () => {
- expect(RasterTileMsgTemp[rasterTileMsgLen + 1].stokes).toEqual(
- assertItem.rasterTileDataGroup[index].stokes
- );
- });
- test(`3rd RASTER_TILE_DATA.stokes = ${assertItem.rasterTileDataGroup[index].stokes}`, () => {
- expect(RasterTileMsgTemp[2 * rasterTileMsgLen + 1].stokes).toEqual(
- assertItem.rasterTileDataGroup[index].stokes
- );
- });
-
- // Check tiles
- const idx1 = rasterTileMsgLen - 1 - 1;
- const idx2 = 2 * rasterTileMsgLen - 1 - 1;
- const idx3 = 3 * rasterTileMsgLen - 1 - 1;
-
- // Check tiles length
- test(`1st RASTER_TILE_DATA.tiles.length = 1`, () => {
- expect(RasterTileMsgTemp[idx1].tiles.length).toEqual(1);
- });
- test(`2nd RASTER_TILE_DATA.tiles.length = 1`, () => {
- expect(RasterTileMsgTemp[idx2].tiles.length).toEqual(1);
- });
- test(`3rd RASTER_TILE_DATA.tiles.length = 1`, () => {
- expect(RasterTileMsgTemp[idx3].tiles.length).toEqual(1);
- });
-
- // Check tiles layer
- test(`1st RASTER_TILE_DATA.tiles.layer = ${assertItem.tileDataGroup[index].layer}`, () => {
- expect(RasterTileMsgTemp[idx1].tiles[0].layer).toEqual(assertItem.tileDataGroup[index].layer);
- });
- test(`2nd RASTER_TILE_DATA.tiles.layer = ${assertItem.tileDataGroup[index].layer}`, () => {
- expect(RasterTileMsgTemp[idx2].tiles[0].layer).toEqual(assertItem.tileDataGroup[index].layer);
- });
- test(`3rd RASTER_TILE_DATA.tiles.layer = ${assertItem.tileDataGroup[index].layer}`, () => {
- expect(RasterTileMsgTemp[idx3].tiles[0].layer).toEqual(assertItem.tileDataGroup[index].layer);
- });
-
- // Check tiles x
- test(`1st RASTER_TILE_DATA.tiles.x = ${assertTileXY}`, () => {
- for (let i = idx1; i > idx1 - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].x);
- }
- });
- test(`2nd RASTER_TILE_DATA.tiles.x = ${assertTileXY}`, () => {
- for (let i = idx2; i > idx2 - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].x);
- }
- });
- test(`3rd RASTER_TILE_DATA.tiles.x = ${assertTileXY}`, () => {
- for (let i = idx3; i > idx3 - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].x);
- }
- });
-
- // Check tiles y
- test(`1st RASTER_TILE_DATA.tiles.y = ${assertTileXY}`, () => {
- for (let i = idx1; i > idx1 - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].y);
- }
- });
- test(`2nd RASTER_TILE_DATA.tiles.y = ${assertTileXY}`, () => {
- for (let i = idx2; i > idx2 - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].y);
- }
- });
- test(`3rd RASTER_TILE_DATA.tiles.y = ${assertTileXY}`, () => {
- for (let i = idx3; i > idx3 - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].y);
- }
- });
-
- // Check tiles width
- test(`1st RASTER_TILE_DATA.tiles.width = ${assertTileWidth}`, () => {
- for (let i = idx1; i > idx1 - rasterTileDataLen; i--) {
- expect(assertTileWidth).toContain(RasterTileMsgTemp[i].tiles[0].width);
- }
- });
- test(`2nd RASTER_TILE_DATA.tiles.width = ${assertTileWidth}`, () => {
- for (let i = idx2; i > idx2 - rasterTileDataLen; i--) {
- expect(assertTileWidth).toContain(RasterTileMsgTemp[i].tiles[0].width);
- }
- });
- test(`3rd RASTER_TILE_DATA.tiles.width = ${assertTileWidth}`, () => {
- for (let i = idx3; i > idx3 - rasterTileDataLen; i--) {
- expect(assertTileWidth).toContain(RasterTileMsgTemp[i].tiles[0].width);
- }
- });
-
- // Check tiles height
- test(`1st RASTER_TILE_DATA.tiles.height = ${assertTileHeight}`, () => {
- for (let i = idx1; i > idx1 - rasterTileDataLen; i--) {
- expect(RasterTileMsgTemp[i].tiles[0].height).toEqual(assertTileHeight);
- }
- });
- test(`2nd RASTER_TILE_DATA.tiles.height = ${assertTileHeight}`, () => {
- for (let i = idx2; i > idx2 - rasterTileDataLen; i--) {
- expect(RasterTileMsgTemp[i].tiles[0].height).toEqual(assertTileHeight);
- }
- });
- test(`3rd RASTER_TILE_DATA.tiles.height = ${assertItem.tileDataGroup[index].height}`, () => {
- for (let i = idx3; i > idx3 - rasterTileDataLen; i--) {
- expect(RasterTileMsgTemp[i].tiles[0].height).toEqual(assertTileHeight);
- }
- });
- });
-
- describe(`2) Set image channel map for the file "${assertItem.fileOpenGroup[index].file}"`, () => {
- let RasterTileMsgTemp: any;
- const rasterTileDataLen = assertItem.setImageChannelGroup2[index].requiredTiles.tiles.length;
- const resterTileMsgLen = rasterTileDataLen + 2;
-
- test(
- `A new RASTER_TILE_DATA should arrive within ${readFileTimeout} ms`,
- async () => {
- msgController.setChannels(assertItem.setImageChannelGroup2[index]);
- const maxChan = assertItem.setImageChannelGroup2[index].channelRange.max;
- const minChan = assertItem.setImageChannelGroup2[index].channelRange.min;
- const channels = maxChan - minChan + 1;
- RasterTileMsgTemp = await ChannelMapStream(rasterTileDataLen, channels);
- },
- readFileTimeout
- );
-
- // Check channels
- test(`New RASTER_TILE_DATA.channel = ${assertItem.setImageChannelGroup2[index].channel}`, () => {
- expect(RasterTileMsgTemp[1].channel).toEqual(assertItem.setImageChannelGroup2[index].channel);
- });
-
- // Check stokes
- test(`New RASTER_TILE_DATA.stokes = ${assertItem.setImageChannelGroup2[index].stokes}`, () => {
- expect(RasterTileMsgTemp[1].stokes).toEqual(assertItem.setImageChannelGroup2[index].stokes);
- });
-
- // Check tiles
- const idx = resterTileMsgLen - 1 - 1;
-
- // Check tiles length
- test(`New RASTER_TILE_DATA.tiles.length = 1`, () => {
- expect(RasterTileMsgTemp[idx].tiles.length).toEqual(1);
- });
-
- // Check tiles layer
- test(`New RASTER_TILE_DATA.tiles.layer = ${assertItem.tileDataGroup[index].layer}`, () => {
- expect(RasterTileMsgTemp[idx].tiles[0].layer).toEqual(assertItem.tileDataGroup[index].layer);
- });
-
- // Check tiles x
- test(`New RASTER_TILE_DATA.tiles.x = ${assertTileXY}`, () => {
- for (let i = idx; i > idx - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].x);
- }
- });
-
- // Check tiles y
- test(`New RASTER_TILE_DATA.tiles.y = ${assertTileXY}`, () => {
- for (let i = idx; i > idx - rasterTileDataLen; i--) {
- expect(assertTileXY).toContain(RasterTileMsgTemp[i].tiles[0].y);
- }
- });
-
- // Check tiles width
- test(`New RASTER_TILE_DATA.tiles.width = ${assertTileWidth}`, () => {
- for (let i = idx; i > idx - rasterTileDataLen; i--) {
- expect(assertTileWidth).toContain(RasterTileMsgTemp[i].tiles[0].width);
- }
- });
- // Check tiles height
- test(`New RASTER_TILE_DATA.tiles.height = ${assertTileHeight}`, () => {
- for (let i = idx; i > idx - rasterTileDataLen; i--) {
- expect(RasterTileMsgTemp[i].tiles[0].height).toEqual(assertTileHeight);
- }
+ // Check the flow control acknowledgment
+ test(`CHANNEL_MAP_FLOW_CONTROL.file_id = ${fileId}`, () => {
+ expect(response.flowControl.fileId).toEqual(fileId);
+ });
+ test(`CHANNEL_MAP_FLOW_CONTROL.completed_channel = ${channel}`, () => {
+ expect(response.flowControl.completedChannel).toEqual(channel);
+ });
+ test(`CHANNEL_MAP_FLOW_CONTROL.status = COMPLETED`, () => {
+ expect(response.flowControl.status).toEqual(CARTA.ChannelMapFlowControl.Status.COMPLETED);
+ });
+
+ // Check channel / stokes of the raster tiles (first RasterTileData follows the sync start)
+ test(`RASTER_TILE_DATA.file_id = ${fileId}`, () => {
+ expect(response.rasterTileMsgs[1].fileId).toEqual(fileId);
+ });
+ test(`RASTER_TILE_DATA.channel = ${channel}`, () => {
+ expect(response.rasterTileMsgs[1].channel).toEqual(channel);
+ });
+ test(`RASTER_TILE_DATA.stokes = 0`, () => {
+ expect(response.rasterTileMsgs[1].stokes).toEqual(0);
+ });
+
+ // Check tiles (RasterTileData occupy indices 1 .. rasterTileDataLen)
+ test(`each RASTER_TILE_DATA.tiles.length = 1`, () => {
+ for (let i = 1; i <= rasterTileDataLen; i++) {
+ expect(response.rasterTileMsgs[i].tiles.length).toEqual(1);
+ }
+ });
+ test(`each RASTER_TILE_DATA.tiles.layer = ${assertItem.tileData.layer}`, () => {
+ for (let i = 1; i <= rasterTileDataLen; i++) {
+ expect(response.rasterTileMsgs[i].tiles[0].layer).toEqual(assertItem.tileData.layer);
+ }
+ });
+ test(`each RASTER_TILE_DATA.tiles.x in ${assertItem.tileData.x}`, () => {
+ for (let i = 1; i <= rasterTileDataLen; i++) {
+ expect(assertItem.tileData.x).toContain(response.rasterTileMsgs[i].tiles[0].x);
+ }
+ });
+ test(`each RASTER_TILE_DATA.tiles.y in ${assertItem.tileData.y}`, () => {
+ for (let i = 1; i <= rasterTileDataLen; i++) {
+ expect(assertItem.tileData.y).toContain(response.rasterTileMsgs[i].tiles[0].y);
+ }
+ });
+ test(`each RASTER_TILE_DATA.tiles.width in ${assertItem.tileData.width}`, () => {
+ for (let i = 1; i <= rasterTileDataLen; i++) {
+ expect(assertItem.tileData.width).toContain(response.rasterTileMsgs[i].tiles[0].width);
+ }
+ });
+ test(`each RASTER_TILE_DATA.tiles.height = ${assertItem.tileData.height}`, () => {
+ for (let i = 1; i <= rasterTileDataLen; i++) {
+ expect(response.rasterTileMsgs[i].tiles[0].height).toEqual(assertItem.tileData.height);
+ }
+ });
});
});
});
diff --git a/src/test/FILEINFO_EXCEPTIONS.test.ts b/src/test/FILEINFO_EXCEPTIONS.test.ts
index 57e2e50..1f15dbf 100644
--- a/src/test/FILEINFO_EXCEPTIONS.test.ts
+++ b/src/test/FILEINFO_EXCEPTIONS.test.ts
@@ -59,4 +59,4 @@ describe('FILEINFO_EXCEPTIONS: Testing error handle of file info generation', ()
});
afterAll(() => msgController.closeConnection());
});
-});
\ No newline at end of file
+});
diff --git a/src/test/MessageController.ts b/src/test/MessageController.ts
index b1971f6..bbd356b 100644
--- a/src/test/MessageController.ts
+++ b/src/test/MessageController.ts
@@ -94,6 +94,7 @@ export class MessageController {
readonly fittingProgressStream: Subject;
readonly vectorTileStream: Subject;
readonly pvPreviewStream: Subject;
+ readonly channelMapFlowControlStream: Subject;
private readonly decoderMap: Map;
private constructor() {
@@ -123,6 +124,7 @@ export class MessageController {
this.fittingProgressStream = new Subject();
this.vectorTileStream = new Subject();
this.pvPreviewStream = new Subject();
+ this.channelMapFlowControlStream = new Subject();
// Construct handler and decoder maps
this.decoderMap = new Map([
@@ -378,6 +380,13 @@ export class MessageController {
handler: this.onDeferredResponse,
},
],
+ [
+ CARTA.EventType.CHANNEL_MAP_FLOW_CONTROL,
+ {
+ messageClass: CARTA.ChannelMapFlowControl,
+ handler: this.onStreamedChannelMapFlowControl,
+ },
+ ],
]);
// check ping every 5 seconds
@@ -412,7 +421,9 @@ export class MessageController {
if (wasNeverActive) {
const def = this.deferredMap.get(requestId);
if (def) {
- def.reject(new Error(`WebSocket closed before connect: code=${ev.code} reason=${ev.reason}`));
+ def.reject(
+ new Error(`WebSocket closed before connect: code=${ev.code} reason=${ev.reason}`)
+ );
this.deferredMap.delete(requestId);
}
}
@@ -848,16 +859,12 @@ export class MessageController {
let stokes = input.stokes;
let requiredTiles = input.requiredTiles;
let channelMapEnabled = input.channelMapEnabled;
- let channelRange = input.channelRange;
- let currentRange = input.currentRange;
if (this.connectionStatus === ConnectionStatus.ACTIVE) {
const message = CARTA.SetImageChannels.create({
fileId: fileId,
channel: channel,
stokes: stokes,
requiredTiles: requiredTiles,
- channelRange: channelRange,
- currentRange: currentRange,
channelMapEnabled: channelMapEnabled,
});
this.logEvent(CARTA.EventType.SET_IMAGE_CHANNELS, this.eventCounter, message, false);
@@ -1030,14 +1037,12 @@ export class MessageController {
let tiles = input.tiles;
let type = input.compressionType;
let quality = input.compressionQuality;
- let currentTiles = input.currentTiles;
if (this.connectionStatus === ConnectionStatus.ACTIVE) {
const message = CARTA.AddRequiredTiles.create({
fileId: fileId,
tiles: tiles,
compressionType: type,
compressionQuality: quality,
- currentTiles: currentTiles,
});
this.logEvent(CARTA.EventType.ADD_REQUIRED_TILES, this.eventCounter, message, false);
if (this.sendEvent(CARTA.EventType.ADD_REQUIRED_TILES, CARTA.AddRequiredTiles.encode(message).finish())) {
@@ -1468,6 +1473,10 @@ export class MessageController {
this.pvPreviewStream.next(previewData);
}
+ private onStreamedChannelMapFlowControl(_eventId: number, flowControl: CARTA.ChannelMapFlowControl) {
+ this.channelMapFlowControlStream.next(flowControl);
+ }
+
private sendEvent(eventType: CARTA.EventType, payload: Uint8Array): boolean {
if (this.connection.readyState === WebSocket.OPEN) {
const eventData = new Uint8Array(8 + payload.byteLength);
diff --git a/src/test/MyClient.ts b/src/test/MyClient.ts
index a295366..176695b 100644
--- a/src/test/MyClient.ts
+++ b/src/test/MyClient.ts
@@ -155,24 +155,41 @@ function Stream(cartaType: any, InputNum?: number) {
});
}
-function ChannelMapStream(rasterTileDataLen: number, channels: number) {
- return new Promise((resolve, reject) => {
+// Collects the response to a single channel map request. Since the refactor, the backend
+// processes one channel per SET_IMAGE_CHANNELS request and replies with that channel's raster
+// tiles (2 RasterTileSync + rasterTileDataLen RasterTileData) followed by a single
+// CHANNEL_MAP_FLOW_CONTROL message reporting the request outcome. Resolves once both the tile
+// batch and the flow control message have arrived.
+function ChannelMapStream(rasterTileDataLen: number) {
+ return new Promise<{ rasterTileMsgs: any[]; flowControl: any }>((resolve, reject) => {
const msgController = MessageController.Instance;
- const rasterTileMsgLen = (rasterTileDataLen + 2) * channels; // # of RasterTileData + 2 RasterTileSync per channel
+ const rasterTileMsgLen = rasterTileDataLen + 2; // # of RasterTileData + 2 RasterTileSync for one channel
let count = 0;
let rasterTileMsgs: any[] = [];
- let rasterTileSyncStream = msgController.rasterSyncStream.pipe(take(2 * channels)); // 2 RasterTileSync per channel
+ let flowControl: any = null;
+
+ const tryResolve = () => {
+ if (count === rasterTileMsgLen && flowControl !== null) {
+ resolve({ rasterTileMsgs, flowControl });
+ }
+ };
+
+ const rasterTileSyncStream = msgController.rasterSyncStream.pipe(take(2)); // start + end
rasterTileSyncStream.subscribe((data) => {
count++;
rasterTileMsgs.push(data);
- if (data.endSync && count === rasterTileMsgLen) {
- resolve(rasterTileMsgs);
- }
+ tryResolve();
});
- let rasterTileDataStream = msgController.rasterTileStream.pipe(take(rasterTileDataLen * channels));
+ const rasterTileDataStream = msgController.rasterTileStream.pipe(take(rasterTileDataLen));
rasterTileDataStream.subscribe((data) => {
count++;
rasterTileMsgs.push(data);
+ tryResolve();
+ });
+ const flowControlStream = msgController.channelMapFlowControlStream.pipe(take(1));
+ flowControlStream.subscribe((data) => {
+ flowControl = data;
+ tryResolve();
});
});
}
diff --git a/src/test/config.json b/src/test/config.json
index 1902afa..1f91f91 100644
--- a/src/test/config.json
+++ b/src/test/config.json
@@ -1,6 +1,6 @@
{
"serverURL0": "ws://127.0.0.1:3002",
- "icdVersion": 30,
+ "icdVersion": 31,
"path": {
"root": ".",
"base": "$BASE",