@cytario/web 2.1.4 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +20 -20
  2. package/app/.server/auth/README.md +8 -8
  3. package/app/.server/auth/authMiddleware.ts +9 -25
  4. package/app/.server/auth/exchangeAuthCode.ts +2 -6
  5. package/app/.server/auth/getS3Client.ts +3 -13
  6. package/app/.server/auth/getSessionCredentials.ts +6 -20
  7. package/app/.server/auth/getUserInfo.ts +2 -6
  8. package/app/.server/auth/keycloakAdmin/client.ts +2 -9
  9. package/app/.server/auth/keycloakAdmin/groups.ts +9 -26
  10. package/app/.server/auth/keycloakAdmin/users.ts +7 -23
  11. package/app/.server/auth/oauthState.ts +4 -13
  12. package/app/.server/auth/redirectIfAuthenticated.ts +1 -3
  13. package/app/.server/auth/refreshAuthTokens.ts +5 -19
  14. package/app/.server/auth/sessionMiddleware.ts +1 -4
  15. package/app/.server/auth/sessionStorage.ts +1 -4
  16. package/app/.server/auth/verifyIdToken.ts +1 -3
  17. package/app/.server/auth/wellKnownEndpoints.ts +1 -4
  18. package/app/.server/db/redis.ts +5 -1
  19. package/app/.server/logging.ts +1 -4
  20. package/app/.server/requestDurationMiddleware.ts +1 -4
  21. package/app/components/.client/ImageViewer/README.md +5 -5
  22. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsController.tsx +7 -9
  23. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerBrightfieldItem.tsx +1 -2
  24. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItem.tsx +2 -5
  25. package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItemList.tsx +7 -15
  26. package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorPicker.tsx +2 -9
  27. package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorSwatch.tsx +1 -4
  28. package/app/components/.client/ImageViewer/components/ChannelsController/DomainSlider.tsx +1 -3
  29. package/app/components/.client/ImageViewer/components/ChannelsController/Histogram.tsx +16 -18
  30. package/app/components/.client/ImageViewer/components/ChannelsController/HistogramChannel.tsx +2 -8
  31. package/app/components/.client/ImageViewer/components/ChannelsController/MinMaxSettings.tsx +6 -15
  32. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarDragHandle.tsx +1 -5
  33. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarToggle.tsx +1 -5
  34. package/app/components/.client/ImageViewer/components/FeatureBar/FeatureItem.tsx +1 -5
  35. package/app/components/.client/ImageViewer/components/FeatureBar/Presets.tsx +3 -11
  36. package/app/components/.client/ImageViewer/components/FeatureBar/useFeatureBar.tsx +16 -25
  37. package/app/components/.client/ImageViewer/components/Image/Channels/useChannelsLayer.ts +7 -18
  38. package/app/components/.client/ImageViewer/components/Image/ImageContainer.tsx +1 -1
  39. package/app/components/.client/ImageViewer/components/Image/ImagePanel.tsx +6 -26
  40. package/app/components/.client/ImageViewer/components/Image/ImagePreview.tsx +2 -9
  41. package/app/components/.client/ImageViewer/components/Image/Overlays/AdditivePolygonLayer.tsx +1 -5
  42. package/app/components/.client/ImageViewer/components/Image/Overlays/AdditiveScatterplotLayer.tsx +1 -5
  43. package/app/components/.client/ImageViewer/components/Image/Overlays/OverlaysLayer.tsx +6 -24
  44. package/app/components/.client/ImageViewer/components/Image/Overlays/markerUniforms.ts +2 -5
  45. package/app/components/.client/ImageViewer/components/Image/Overlays/useOverlaysLayer.tsx +7 -21
  46. package/app/components/.client/ImageViewer/components/Image/useInitializeChannels.ts +1 -7
  47. package/app/components/.client/ImageViewer/components/Image/useResizeObserver.ts +1 -1
  48. package/app/components/.client/ImageViewer/components/Magnifier.tsx +5 -13
  49. package/app/components/.client/ImageViewer/components/Measurements/ActiveViewStatePreview.tsx +3 -8
  50. package/app/components/.client/ImageViewer/components/Measurements/CursorTick.tsx +2 -7
  51. package/app/components/.client/ImageViewer/components/Measurements/Ruler.tsx +1 -8
  52. package/app/components/.client/ImageViewer/components/Measurements/SlideCarrier.tsx +3 -13
  53. package/app/components/.client/ImageViewer/components/Measurements/Tick.tsx +1 -1
  54. package/app/components/.client/ImageViewer/components/Measurements/calculateViewStateToFit.ts +1 -1
  55. package/app/components/.client/ImageViewer/components/Measurements/useMeasurements.ts +9 -28
  56. package/app/components/.client/ImageViewer/components/OverlaysController/AddOverlay.tsx +4 -13
  57. package/app/components/.client/ImageViewer/components/OverlaysController/OverlayPicker.modal.tsx +1 -6
  58. package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.Item.tsx +24 -54
  59. package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.tsx +1 -3
  60. package/app/components/.client/ImageViewer/components/SplitViewToggle.tsx +1 -3
  61. package/app/components/.client/ImageViewer/components/ViewerHeader.tsx +1 -3
  62. package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.d.ts +9 -11
  63. package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.js +11 -11
  64. package/app/components/.client/ImageViewer/state/decoders/decoder.worker.js +49 -49
  65. package/app/components/.client/ImageViewer/state/decoders/genericDecoder.ts +76 -81
  66. package/app/components/.client/ImageViewer/state/decoders/jp2k-decoder.ts +9 -9
  67. package/app/components/.client/ImageViewer/state/decoders/lzwDecoder.ts +9 -9
  68. package/app/components/.client/ImageViewer/state/loaders/loadBioformatsZarrWithCredentials.ts +10 -22
  69. package/app/components/.client/ImageViewer/state/store/ViewerStoreContext.tsx +4 -18
  70. package/app/components/.client/ImageViewer/state/store/createViewerStore.ts +110 -194
  71. package/app/components/.client/ImageViewer/state/store/getInitialChannelsState.ts +2 -6
  72. package/app/components/.client/ImageViewer/state/store/selectors.ts +9 -9
  73. package/app/components/.client/ImageViewer/state/store/types.ts +3 -12
  74. package/app/components/.client/ImageViewer/state/transport/CredentialedHTTPStore.ts +1 -5
  75. package/app/components/.client/ImageViewer/state/transport/SigV4TiffClient.ts +2 -9
  76. package/app/components/.client/ImageViewer/utils/getSelectionStats.ts +1 -4
  77. package/app/components/.client/ImageViewer/utils/handleImageViewerHover.ts +1 -1
  78. package/app/components/.client/ImageViewer/utils/mapChannelConfigsToState.ts +2 -4
  79. package/app/components/.client/ImageViewer/utils/useTilesLoading.ts +3 -3
  80. package/app/components/AppHeader.tsx +1 -4
  81. package/app/components/Breadcrumbs/Breadcrumbs.tsx +4 -13
  82. package/app/components/Breadcrumbs/getCrumbs.tsx +1 -1
  83. package/app/components/ClientOnly.tsx +1 -1
  84. package/app/components/Container.tsx +3 -15
  85. package/app/components/DataGrid/ConvertOverlay.modal.tsx +1 -6
  86. package/app/components/DataGrid/DataGrid.tsx +7 -27
  87. package/app/components/DataGrid/WktSvg.tsx +2 -4
  88. package/app/components/DataGrid/getParquetSchema.ts +1 -4
  89. package/app/components/DescriptionList.tsx +1 -3
  90. package/app/components/DirectoryView/ConnectionMenu.tsx +8 -22
  91. package/app/components/DirectoryView/DirectoryView.tsx +10 -46
  92. package/app/components/DirectoryView/DirectoryViewGrid.tsx +16 -49
  93. package/app/components/DirectoryView/DirectoryViewTableConnection.tsx +1 -4
  94. package/app/components/DirectoryView/DirectoryViewTableDirectory.tsx +2 -7
  95. package/app/components/DirectoryView/DirectoryViewTree.tsx +5 -21
  96. package/app/components/DirectoryView/FilterBar.tsx +9 -48
  97. package/app/components/DirectoryView/buildDirectoryTree.ts +6 -25
  98. package/app/components/DirectoryView/filterNodes.ts +4 -11
  99. package/app/components/DirectoryView/modals/Cyberduck.modal.tsx +6 -15
  100. package/app/components/DirectoryView/modals/FileInfo.modal.tsx +1 -5
  101. package/app/components/DirectoryView/useLayoutStore.ts +5 -25
  102. package/app/components/GlobalSearch/GlobalSearch.tsx +1 -4
  103. package/app/components/GlobalSearch/SearchBar.tsx +1 -7
  104. package/app/components/GlobalSearch/Suggestions.tsx +0 -1
  105. package/app/components/ImageViewer/state/formatRegistry.ts +5 -18
  106. package/app/components/LavaLoader.tsx +4 -11
  107. package/app/components/Layout/Footer.tsx +1 -4
  108. package/app/components/Pills/ScopePill.tsx +2 -8
  109. package/app/components/Table/ColumnFilterInput.tsx +5 -20
  110. package/app/components/Table/ColumnResizeHandle.tsx +1 -5
  111. package/app/components/Table/ColumnSortButton.tsx +1 -5
  112. package/app/components/Table/SelectionFooter.tsx +3 -5
  113. package/app/components/Table/Table.tsx +5 -21
  114. package/app/components/Table/TableBodyRow.tsx +19 -31
  115. package/app/components/Table/TableHeaderRow.tsx +7 -28
  116. package/app/components/Table/TableMenu.tsx +4 -20
  117. package/app/components/Table/state/createTableStore.ts +3 -9
  118. package/app/components/Table/state/useTableStore.ts +1 -3
  119. package/app/components/Table/types.ts +4 -10
  120. package/app/components/Table/useColumnFilters.ts +1 -4
  121. package/app/components/Table/useColumnVisibility.ts +4 -11
  122. package/app/components/Table/useColumnWidths.ts +1 -3
  123. package/app/components/Table/useTableSorting.ts +4 -12
  124. package/app/components/Tooltip/Tooltip.tsx +4 -17
  125. package/app/components/Tooltip/TooltipSpan.tsx +5 -28
  126. package/app/components/Tooltip/useCopyToClipboard.ts +1 -3
  127. package/app/components/Tooltip/useMiddleEllipsis.ts +2 -7
  128. package/app/components/Tooltip/useOverflowDetection.ts +2 -5
  129. package/app/components/UserMenu.tsx +2 -9
  130. package/app/entry.server.tsx +9 -19
  131. package/app/hooks/useSearchParam.ts +2 -4
  132. package/app/lib/bootstrapPluginsCore.ts +4 -9
  133. package/app/root.tsx +4 -15
  134. package/app/routes/admin/assertAdminScope.ts +1 -3
  135. package/app/routes/admin/assertGroupPathsInScope.ts +3 -11
  136. package/app/routes/admin/assertGroupsInScope.ts +3 -11
  137. package/app/routes/admin/assertUsersInScope.ts +2 -8
  138. package/app/routes/admin/bulkInvite/bulkInvite.action.ts +2 -13
  139. package/app/routes/admin/bulkInvite/bulkInvite.form.tsx +18 -35
  140. package/app/routes/admin/createGroup/createGroup.action.ts +3 -10
  141. package/app/routes/admin/createGroup/createGroup.form.tsx +2 -9
  142. package/app/routes/admin/createGroup/createGroup.modal.tsx +1 -5
  143. package/app/routes/admin/inviteUser/inviteUser.action.ts +1 -4
  144. package/app/routes/admin/inviteUser/inviteUser.form.tsx +2 -8
  145. package/app/routes/admin/inviteUser/inviteUser.loader.ts +1 -4
  146. package/app/routes/admin/inviteUser/inviteUser.modal.tsx +3 -16
  147. package/app/routes/admin/updateUser/updateUser.form.tsx +4 -15
  148. package/app/routes/admin/updateUser/updateUser.modal.tsx +5 -23
  149. package/app/routes/admin/updateUser/userDetail.action.ts +2 -10
  150. package/app/routes/admin/users/BulkActions.tsx +15 -38
  151. package/app/routes/admin/users/bulkUsers.action.ts +2 -9
  152. package/app/routes/admin/users/bulkUsers.schema.ts +1 -6
  153. package/app/routes/admin/users/users.route.tsx +14 -63
  154. package/app/routes/api/cyberduck-profile.$name.ts +6 -2
  155. package/app/routes/auth/callback.route.tsx +8 -33
  156. package/app/routes/auth/login.route.tsx +8 -11
  157. package/app/routes/auth/logout.route.tsx +4 -14
  158. package/app/routes/config.route.tsx +1 -5
  159. package/app/routes/connections/connection.form.tsx +5 -14
  160. package/app/routes/connections/connection.schema.ts +4 -16
  161. package/app/routes/connections/connections.loader.ts +11 -23
  162. package/app/routes/connections/connections.route.tsx +1 -5
  163. package/app/routes/connections/connections.server.ts +1 -3
  164. package/app/routes/connections/createConnection.action.ts +2 -8
  165. package/app/routes/connections/createConnection.modal.tsx +3 -13
  166. package/app/routes/connections/deleteConnection.action.ts +1 -4
  167. package/app/routes/connections/updateConnection.action.ts +2 -8
  168. package/app/routes/connections/updateConnection.modal.tsx +4 -14
  169. package/app/routes/home/home.route.tsx +8 -33
  170. package/app/routes/layouts/ModalOutlet.tsx +6 -18
  171. package/app/routes/objects/objects.loader.ts +5 -19
  172. package/app/routes/objects/objects.route.tsx +11 -30
  173. package/app/routes/presign.route.tsx +5 -18
  174. package/app/routes/recent.route.tsx +1 -4
  175. package/app/routes/search.route.tsx +4 -17
  176. package/app/routes.ts +1 -4
  177. package/app/tailwind.css +17 -12
  178. package/app/types/cornerstone-codecs.d.ts +25 -29
  179. package/app/utils/connectionsStore/selectors.ts +2 -6
  180. package/app/utils/connectionsStore/useConnectionsStore.ts +2 -6
  181. package/app/utils/db/convertCsvToParquet.ts +1 -3
  182. package/app/utils/db/createDatabase.ts +1 -3
  183. package/app/utils/db/createSingleton.ts +1 -1
  184. package/app/utils/db/getBlobFromObjectNode.ts +3 -9
  185. package/app/utils/db/getGeomQuery.ts +1 -1
  186. package/app/utils/db/getMarkerInfoWasm.ts +1 -3
  187. package/app/utils/db/getTileBoundingBox.ts +1 -4
  188. package/app/utils/db/sqlQueries.ts +1 -4
  189. package/app/utils/fileType.ts +80 -10
  190. package/app/utils/filterObjects.ts +2 -5
  191. package/app/utils/localFilesStore/useFileStore.ts +7 -7
  192. package/app/utils/recentlyViewed.server.ts +1 -4
  193. package/app/utils/resourceId.ts +3 -11
  194. package/app/utils/s3Provider.ts +3 -7
  195. package/app/utils/signedFetch.ts +4 -13
  196. package/bin-src/codegen.ts +4 -1
  197. package/package.json +5 -1
  198. package/prisma/seed.ts +1 -2
  199. package/public/favicon/site.webmanifest +1 -1
  200. package/server.js +1 -4
  201. package/server.js.map +1 -1
  202. package/vite-plugins/cytario-plugins.ts +2 -8
@@ -1,5 +1,5 @@
1
- import openjpegWasmUrl from '@cornerstonejs/codec-openjpeg/decodewasm?url'
2
- import openJpegFactory from '@cornerstonejs/codec-openjpeg/decodewasmjs';
1
+ import openjpegWasmUrl from "@cornerstonejs/codec-openjpeg/decodewasm?url";
2
+ import openJpegFactory from "@cornerstonejs/codec-openjpeg/decodewasmjs";
3
3
 
4
4
  const local = {
5
5
  codec: undefined,
@@ -16,7 +16,7 @@ const local = {
16
16
  */
17
17
  export function loadJp2KDecoder(decodeConfig) {
18
18
  local.decodeConfig = decodeConfig;
19
-
19
+
20
20
  // Return existing codec or loading promise
21
21
  if (local.codec) {
22
22
  return Promise.resolve();
@@ -28,7 +28,7 @@ export function loadJp2KDecoder(decodeConfig) {
28
28
  local.isLoading = true;
29
29
  local.loadPromise = openJpegFactory({
30
30
  locateFile: (f) => {
31
- if (typeof f === 'string' && f.endsWith('.wasm')) {
31
+ if (typeof f === "string" && f.endsWith(".wasm")) {
32
32
  return openjpegWasmUrl;
33
33
  }
34
34
  return f;
@@ -57,30 +57,30 @@ export function loadJp2KDecoder(decodeConfig) {
57
57
  */
58
58
  export async function decodeJPEG2000(compressedImageFrame) {
59
59
  if (!compressedImageFrame || compressedImageFrame.length === 0) {
60
- throw new Error('Invalid compressed image frame: empty or null');
60
+ throw new Error("Invalid compressed image frame: empty or null");
61
61
  }
62
62
 
63
63
  await loadJp2KDecoder();
64
-
64
+
65
65
  if (!local.decoder) {
66
- throw new Error('JPEG2000 decoder not initialized');
66
+ throw new Error("JPEG2000 decoder not initialized");
67
67
  }
68
68
 
69
69
  const decoder = local.decoder;
70
-
70
+
71
71
  // Get encoded buffer and copy data
72
72
  const encodedBufferInWASM = decoder.getEncodedBuffer(compressedImageFrame.length);
73
73
  encodedBufferInWASM.set(compressedImageFrame);
74
-
74
+
75
75
  // Decode the image
76
76
  decoder.decode();
77
-
77
+
78
78
  // Extract decoded data
79
79
  const frameInfo = decoder.getFrameInfo();
80
80
  const decodedBufferInWASM = decoder.getDecodedBuffer();
81
81
  const imageFrame = new Uint8Array(decodedBufferInWASM.length);
82
82
  imageFrame.set(decodedBufferInWASM);
83
-
83
+
84
84
  return {
85
85
  info: frameInfo,
86
86
  pixels: imageFrame,
@@ -6,71 +6,71 @@ import { decodeJPEG2000, cleanupJp2KDecoder } from "./decodeJPEG2000";
6
6
  * Decoder types supported by this worker
7
7
  */
8
8
  const DECODER_TYPES = {
9
- LZW: "lzw-decoder",
10
- JP2K: "jp2k-decoder",
9
+ LZW: "lzw-decoder",
10
+ JP2K: "jp2k-decoder",
11
11
  };
12
12
 
13
13
  /**
14
14
  * Worker message handler for image decoding tasks
15
15
  */
16
16
  self.onmessage = async (event) => {
17
- const { taskId, buffer, maxUncompressedSize, decoderId } = event.data;
17
+ const { taskId, buffer, maxUncompressedSize, decoderId } = event.data;
18
18
 
19
- // Validate input
20
- if (!taskId) {
21
- self.postMessage({
22
- taskId: 'unknown',
23
- error: 'Missing taskId in worker message'
24
- });
25
- return;
26
- }
27
-
28
- if (!buffer || !decoderId) {
29
- self.postMessage({
30
- taskId,
31
- error: 'Missing required parameters: buffer or decoderId'
32
- });
33
- return;
34
- }
19
+ // Validate input
20
+ if (!taskId) {
21
+ self.postMessage({
22
+ taskId: "unknown",
23
+ error: "Missing taskId in worker message",
24
+ });
25
+ return;
26
+ }
35
27
 
36
- try {
37
- const bytes = new Uint8Array(buffer);
38
- let pixels;
28
+ if (!buffer || !decoderId) {
29
+ self.postMessage({
30
+ taskId,
31
+ error: "Missing required parameters: buffer or decoderId",
32
+ });
33
+ return;
34
+ }
39
35
 
40
- switch (decoderId) {
41
- case DECODER_TYPES.LZW: {
42
- if (!maxUncompressedSize) {
43
- throw new Error('maxUncompressedSize required for LZW decoder');
44
- }
45
- const decoded = await decompress(bytes, maxUncompressedSize);
46
- pixels = decoded.buffer;
47
- break;
48
- }
36
+ try {
37
+ const bytes = new Uint8Array(buffer);
38
+ let pixels;
49
39
 
50
- case DECODER_TYPES.JP2K: {
51
- const image = await decodeJPEG2000(bytes);
52
- pixels = image.pixels.buffer;
53
- break;
54
- }
55
-
56
- default:
57
- throw new Error(`Unknown decoder type: ${decoderId}`);
40
+ switch (decoderId) {
41
+ case DECODER_TYPES.LZW: {
42
+ if (!maxUncompressedSize) {
43
+ throw new Error("maxUncompressedSize required for LZW decoder");
58
44
  }
45
+ const decoded = await decompress(bytes, maxUncompressedSize);
46
+ pixels = decoded.buffer;
47
+ break;
48
+ }
49
+
50
+ case DECODER_TYPES.JP2K: {
51
+ const image = await decodeJPEG2000(bytes);
52
+ pixels = image.pixels.buffer;
53
+ break;
54
+ }
59
55
 
60
- // Transfer the ArrayBuffer to avoid copying
61
- self.postMessage({ taskId, result: pixels }, [pixels]);
62
- } catch (error) {
63
- const errorMessage = error instanceof Error ? error.message : String(error);
64
- self.postMessage({
65
- taskId,
66
- error: errorMessage
67
- });
56
+ default:
57
+ throw new Error(`Unknown decoder type: ${decoderId}`);
68
58
  }
59
+
60
+ // Transfer the ArrayBuffer to avoid copying
61
+ self.postMessage({ taskId, result: pixels }, [pixels]);
62
+ } catch (error) {
63
+ const errorMessage = error instanceof Error ? error.message : String(error);
64
+ self.postMessage({
65
+ taskId,
66
+ error: errorMessage,
67
+ });
68
+ }
69
69
  };
70
70
 
71
71
  /**
72
72
  * Cleanup when worker is being terminated
73
73
  */
74
- self.addEventListener('close', () => {
75
- cleanupJp2KDecoder();
74
+ self.addEventListener("close", () => {
75
+ cleanupJp2KDecoder();
76
76
  });
@@ -23,117 +23,112 @@ const getWorkerPool = (): WorkerPool => {
23
23
 
24
24
  // Cache for decoded blocks with LRU eviction
25
25
  const bufferCache = new LRUCache<number, ArrayBuffer>({
26
- max: CACHE_SIZE_LIMIT,
26
+ max: CACHE_SIZE_LIMIT,
27
27
  });
28
28
 
29
29
  export interface FileDirectory {
30
- TileWidth?: number;
31
- ImageWidth?: number;
32
- TileLength?: number;
33
- ImageLength?: number;
34
- BitsPerSample: number[];
30
+ TileWidth?: number;
31
+ ImageWidth?: number;
32
+ TileLength?: number;
33
+ ImageLength?: number;
34
+ BitsPerSample: number[];
35
35
  }
36
36
 
37
37
  /**
38
38
  * Base decoder class for handling image block decoding with worker pool and caching
39
39
  */
40
40
  export class GenericDecoder extends BaseDecoder {
41
- private readonly maxUncompressedSize: number;
41
+ private readonly maxUncompressedSize: number;
42
42
 
43
- constructor(fileDirectory: FileDirectory) {
44
- super();
43
+ constructor(fileDirectory: FileDirectory) {
44
+ super();
45
45
 
46
- if (
47
- !fileDirectory.BitsPerSample ||
48
- fileDirectory.BitsPerSample.length === 0
49
- ) {
50
- throw new Error("FileDirectory must have BitsPerSample defined");
51
- }
46
+ if (!fileDirectory.BitsPerSample || fileDirectory.BitsPerSample.length === 0) {
47
+ throw new Error("FileDirectory must have BitsPerSample defined");
48
+ }
49
+
50
+ const width = fileDirectory.TileWidth || fileDirectory.ImageWidth || 0;
51
+ const height = fileDirectory.TileLength || fileDirectory.ImageLength || 0;
52
+ const nbytes = fileDirectory.BitsPerSample[0] / 8;
52
53
 
53
- const width = fileDirectory.TileWidth || fileDirectory.ImageWidth || 0;
54
- const height =
55
- fileDirectory.TileLength || fileDirectory.ImageLength || 0;
56
- const nbytes = fileDirectory.BitsPerSample[0] / 8;
54
+ this.maxUncompressedSize = width * height * nbytes;
55
+ }
57
56
 
58
- this.maxUncompressedSize = width * height * nbytes;
57
+ /**
58
+ * Decodes a compressed image block using worker pool and caching
59
+ * @param inputBuffer - The compressed image block
60
+ * @returns Decoded image buffer
61
+ */
62
+ async decodeBlock(inputBuffer: ArrayBuffer): Promise<ArrayBuffer> {
63
+ if (!inputBuffer || inputBuffer.byteLength === 0) {
64
+ throw new Error("Invalid input buffer: empty or null");
59
65
  }
60
66
 
61
- /**
62
- * Decodes a compressed image block using worker pool and caching
63
- * @param inputBuffer - The compressed image block
64
- * @returns Decoded image buffer
65
- */
66
- async decodeBlock(inputBuffer: ArrayBuffer): Promise<ArrayBuffer> {
67
- if (!inputBuffer || inputBuffer.byteLength === 0) {
68
- throw new Error("Invalid input buffer: empty or null");
69
- }
70
-
71
- const bufferHash = this.hashBuffer(inputBuffer);
72
-
73
- // Check cache first
74
- const cachedResult = bufferCache.get(bufferHash);
75
- if (cachedResult) {
76
- return cachedResult;
77
- }
78
-
79
- try {
80
- const outputBuffer = await getWorkerPool().runTask({
81
- buffer: inputBuffer,
82
- maxUncompressedSize: this.maxUncompressedSize,
83
- decoderId: this.getDecoderId(),
84
- });
85
-
86
- // Cache the result
87
- bufferCache.set(bufferHash, outputBuffer);
88
- return outputBuffer;
89
- } catch (error) {
90
- const errorMessage =
91
- error instanceof Error ? error.message : String(error);
92
- throw new Error(`Failed to decode block: ${errorMessage}`);
93
- }
67
+ const bufferHash = this.hashBuffer(inputBuffer);
68
+
69
+ // Check cache first
70
+ const cachedResult = bufferCache.get(bufferHash);
71
+ if (cachedResult) {
72
+ return cachedResult;
94
73
  }
95
74
 
96
- /**
97
- * Returns the decoder identifier (must be overridden by subclasses)
98
- */
99
- public getDecoderId(): string {
100
- return "uninitialized-decoder";
75
+ try {
76
+ const outputBuffer = await getWorkerPool().runTask({
77
+ buffer: inputBuffer,
78
+ maxUncompressedSize: this.maxUncompressedSize,
79
+ decoderId: this.getDecoderId(),
80
+ });
81
+
82
+ // Cache the result
83
+ bufferCache.set(bufferHash, outputBuffer);
84
+ return outputBuffer;
85
+ } catch (error) {
86
+ const errorMessage = error instanceof Error ? error.message : String(error);
87
+ throw new Error(`Failed to decode block: ${errorMessage}`);
101
88
  }
89
+ }
102
90
 
103
- /**
104
- * FNV-1a hash function for ArrayBuffer - faster and better distribution than simple hash
105
- * @param buffer - The buffer to hash
106
- * @returns Hash value
107
- */
108
- private hashBuffer(buffer: ArrayBuffer): number {
109
- const FNV_OFFSET_BASIS = 2166136261;
110
- const FNV_PRIME = 16777619;
111
-
112
- let hash = FNV_OFFSET_BASIS;
113
- const view = new Uint8Array(buffer);
114
- const len = view.length;
115
-
116
- for (let i = 0; i < len; i++) {
117
- hash ^= view[i];
118
- hash = Math.imul(hash, FNV_PRIME);
119
- }
120
-
121
- return hash >>> 0; // Convert to unsigned 32-bit integer
91
+ /**
92
+ * Returns the decoder identifier (must be overridden by subclasses)
93
+ */
94
+ public getDecoderId(): string {
95
+ return "uninitialized-decoder";
96
+ }
97
+
98
+ /**
99
+ * FNV-1a hash function for ArrayBuffer - faster and better distribution than simple hash
100
+ * @param buffer - The buffer to hash
101
+ * @returns Hash value
102
+ */
103
+ private hashBuffer(buffer: ArrayBuffer): number {
104
+ const FNV_OFFSET_BASIS = 2166136261;
105
+ const FNV_PRIME = 16777619;
106
+
107
+ let hash = FNV_OFFSET_BASIS;
108
+ const view = new Uint8Array(buffer);
109
+ const len = view.length;
110
+
111
+ for (let i = 0; i < len; i++) {
112
+ hash ^= view[i];
113
+ hash = Math.imul(hash, FNV_PRIME);
122
114
  }
115
+
116
+ return hash >>> 0; // Convert to unsigned 32-bit integer
117
+ }
123
118
  }
124
119
 
125
120
  /**
126
121
  * Clears the decoder cache
127
122
  */
128
123
  export function clearDecoderCache(): void {
129
- bufferCache.clear();
124
+ bufferCache.clear();
130
125
  }
131
126
 
132
127
  /**
133
128
  * Terminates the worker pool and releases resources
134
129
  */
135
130
  export function shutdownDecoderPool(): void {
136
- workerPool?.terminate();
137
- workerPool = null;
138
- bufferCache.clear();
131
+ workerPool?.terminate();
132
+ workerPool = null;
133
+ bufferCache.clear();
139
134
  }
@@ -5,14 +5,14 @@ import { GenericDecoder, FileDirectory } from "./genericDecoder";
5
5
  * Used for decoding JPEG2000-compressed image data
6
6
  */
7
7
  export class JP2KDecoder extends GenericDecoder {
8
- constructor(fileDirectory: FileDirectory) {
9
- super(fileDirectory);
10
- }
8
+ constructor(fileDirectory: FileDirectory) {
9
+ super(fileDirectory);
10
+ }
11
11
 
12
- /**
13
- * Returns the decoder identifier for the worker pool
14
- */
15
- public getDecoderId(): string {
16
- return "jp2k-decoder";
17
- }
12
+ /**
13
+ * Returns the decoder identifier for the worker pool
14
+ */
15
+ public getDecoderId(): string {
16
+ return "jp2k-decoder";
17
+ }
18
18
  }
@@ -5,14 +5,14 @@ import { GenericDecoder, FileDirectory } from "./genericDecoder";
5
5
  * Used for decoding LZW-compressed TIFF image data
6
6
  */
7
7
  export class LZWDecoder extends GenericDecoder {
8
- constructor(fileDirectory: FileDirectory) {
9
- super(fileDirectory);
10
- }
8
+ constructor(fileDirectory: FileDirectory) {
9
+ super(fileDirectory);
10
+ }
11
11
 
12
- /**
13
- * Returns the decoder identifier for the worker pool
14
- */
15
- public getDecoderId(): string {
16
- return "lzw-decoder";
17
- }
12
+ /**
13
+ * Returns the decoder identifier for the worker pool
14
+ */
15
+ public getDecoderId(): string {
16
+ return "lzw-decoder";
17
+ }
18
18
  }
@@ -59,15 +59,11 @@ export function rootAttrsToImage(rootAttrs: RootAttrs, loader: Loader): Image {
59
59
  const SizeT = shape[dimIndex("t")] ?? 1;
60
60
  const SizeC = shape[dimIndex("c")] ?? (channels.length || 1);
61
61
 
62
- const { PhysicalSizeX, PhysicalSizeY, PhysicalSizeZ } = extractPhysicalSizes(
63
- multiscale,
64
- axes,
65
- );
62
+ const { PhysicalSizeX, PhysicalSizeY, PhysicalSizeZ } = extractPhysicalSizes(multiscale, axes);
66
63
 
67
64
  const axisUnit = (name: string) => {
68
65
  const axis = axes.find(
69
- (a): a is { name: string; unit?: string } =>
70
- typeof a !== "string" && a.name === name,
66
+ (a): a is { name: string; unit?: string } => typeof a !== "string" && a.name === name,
71
67
  );
72
68
  return axis?.unit ?? "µm";
73
69
  };
@@ -94,13 +90,11 @@ export function rootAttrsToImage(rootAttrs: RootAttrs, loader: Loader): Image {
94
90
  PhysicalSizeXUnit: axisUnit("x"),
95
91
  PhysicalSizeYUnit: axisUnit("y"),
96
92
  PhysicalSizeZUnit: axisUnit("z"),
97
- Channels: channels.map(
98
- (ch: { label: string; color: string }, i: number) => ({
99
- ID: `Channel:0:${i}`,
100
- Name: ch.label,
101
- Color: parseOmeroColor(ch.color),
102
- }),
103
- ),
93
+ Channels: channels.map((ch: { label: string; color: string }, i: number) => ({
94
+ ID: `Channel:0:${i}`,
95
+ Name: ch.label,
96
+ Color: parseOmeroColor(ch.color),
97
+ })),
104
98
  },
105
99
  } as Image;
106
100
  }
@@ -112,18 +106,14 @@ export function extractPhysicalSizes(
112
106
  ) {
113
107
  const datasets = (multiscale?.datasets ?? []) as DatasetWithTransforms[];
114
108
  const firstDataset = datasets[0];
115
- const scaleTransform = firstDataset?.coordinateTransformations?.find(
116
- (t) => t.type === "scale",
117
- );
109
+ const scaleTransform = firstDataset?.coordinateTransformations?.find((t) => t.type === "scale");
118
110
 
119
111
  if (!scaleTransform?.scale || !axes) {
120
112
  return {};
121
113
  }
122
114
 
123
115
  const resolvedAxes =
124
- axes.map((a: string | { name: string }) =>
125
- typeof a === "string" ? a : a.name,
126
- ) ?? [];
116
+ axes.map((a: string | { name: string }) => (typeof a === "string" ? a : a.name)) ?? [];
127
117
 
128
118
  const scaleForAxis = (name: string): number | undefined => {
129
119
  const idx = resolvedAxes.indexOf(name);
@@ -141,9 +131,7 @@ export function extractPhysicalSizes(
141
131
  * Omero colors are hex strings — "FF0000" (RGB) or "FF0000FF" (RGBA).
142
132
  * Exported for testing.
143
133
  */
144
- export function parseOmeroColor(
145
- color: string,
146
- ): [number, number, number, number] | undefined {
134
+ export function parseOmeroColor(color: string): [number, number, number, number] | undefined {
147
135
  if (!color) return undefined;
148
136
 
149
137
  const hex = color.replace(/^#/, "");
@@ -82,32 +82,18 @@ interface ViewerStoreProviderProps {
82
82
  }
83
83
 
84
84
  // Viewer is auth-agnostic — caller owns URL construction and signing.
85
- export const ViewerStoreProvider = ({
86
- url,
87
- signedFetch,
88
- children,
89
- }: ViewerStoreProviderProps) => {
85
+ export const ViewerStoreProvider = ({ url, signedFetch, children }: ViewerStoreProviderProps) => {
90
86
  const registerViewer = useViewerRegistryStore((s) => s.registerViewer);
91
87
 
92
- const store = useMemo(
93
- () => registerViewer(url, signedFetch),
94
- [url, signedFetch, registerViewer],
95
- );
88
+ const store = useMemo(() => registerViewer(url, signedFetch), [url, signedFetch, registerViewer]);
96
89
 
97
- return (
98
- <ViewerStoreContext.Provider value={store}>
99
- {children}
100
- </ViewerStoreContext.Provider>
101
- );
90
+ return <ViewerStoreContext.Provider value={store}>{children}</ViewerStoreContext.Provider>;
102
91
  };
103
92
 
104
93
  /** Access the viewer store from within a ViewerStoreProvider. */
105
94
  export const useViewerStore = <T,>(selector: (state: ViewerStore) => T): T => {
106
95
  const store = useContext(ViewerStoreContext);
107
96
 
108
- if (!store)
109
- throw new Error(
110
- "useViewerStoreContext must be used within ViewerStoreProvider",
111
- );
97
+ if (!store) throw new Error("useViewerStoreContext must be used within ViewerStoreProvider");
112
98
  return useStore(store, selector);
113
99
  };