@vitessce/config 3.0.0 → 3.1.0

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.
package/dist/index.js CHANGED
@@ -93,6 +93,10 @@ const FileType$1 = {
93
93
  ANNDATA_EXPRESSION_MATRIX_ZARR: "anndata-expression-matrix.zarr"
94
94
  };
95
95
  const CoordinationType$1 = {
96
+ // Meta coordination scopes
97
+ META_COORDINATION_SCOPES: "metaCoordinationScopes",
98
+ META_COORDINATION_SCOPES_BY: "metaCoordinationScopesBy",
99
+ // Other coordination scopes
96
100
  DATASET: "dataset",
97
101
  // Entity types
98
102
  OBS_TYPE: "obsType",
@@ -15245,6 +15249,72 @@ class VitessceConfigDataset {
15245
15249
  };
15246
15250
  }
15247
15251
  }
15252
+ function useComplexCoordinationHelper(scopes, coordinationScopes, coordinationScopesBy) {
15253
+ function processLevel(parentType, parentScope, levelType, levelVal) {
15254
+ var _a, _b;
15255
+ if (Array.isArray(levelVal)) {
15256
+ coordinationScopesBy[parentType] = {
15257
+ ...coordinationScopesBy[parentType] || {},
15258
+ [levelType]: {
15259
+ ...((_a = coordinationScopesBy[parentType]) == null ? void 0 : _a[levelType]) || {},
15260
+ [parentScope.cScope]: levelVal.map((childVal) => childVal.scope.cScope)
15261
+ }
15262
+ };
15263
+ levelVal.forEach((childVal) => {
15264
+ if (childVal.children) {
15265
+ Object.entries(childVal.children).forEach(([nextLevelType, nextLevelVal]) => processLevel(
15266
+ levelType,
15267
+ childVal.scope,
15268
+ nextLevelType,
15269
+ nextLevelVal
15270
+ ));
15271
+ }
15272
+ });
15273
+ } else {
15274
+ coordinationScopesBy[parentType] = {
15275
+ ...coordinationScopesBy[parentType] || {},
15276
+ [levelType]: {
15277
+ ...((_b = coordinationScopesBy[parentType]) == null ? void 0 : _b[levelType]) || {},
15278
+ [parentScope.cScope]: levelVal.scope.cScope
15279
+ }
15280
+ };
15281
+ if (levelVal.children) {
15282
+ Object.entries(levelVal.children).forEach(([nextLevelType, nextLevelVal]) => processLevel(
15283
+ levelType,
15284
+ levelVal.scope,
15285
+ nextLevelType,
15286
+ nextLevelVal
15287
+ ));
15288
+ }
15289
+ }
15290
+ }
15291
+ Object.entries(scopes).forEach(([topLevelType, topLevelVal]) => {
15292
+ if (Array.isArray(topLevelVal)) {
15293
+ coordinationScopes[topLevelType] = topLevelVal.map((levelVal) => levelVal.scope.cScope);
15294
+ topLevelVal.forEach((levelVal) => {
15295
+ if (levelVal.children) {
15296
+ Object.entries(levelVal.children).forEach(([nextLevelType, nextLevelVal]) => processLevel(
15297
+ topLevelType,
15298
+ levelVal.scope,
15299
+ nextLevelType,
15300
+ nextLevelVal
15301
+ ));
15302
+ }
15303
+ });
15304
+ } else {
15305
+ coordinationScopes[topLevelType] = topLevelVal.scope.cScope;
15306
+ if (topLevelVal.children) {
15307
+ Object.entries(topLevelVal.children).forEach(([nextLevelType, nextLevelVal]) => processLevel(
15308
+ topLevelType,
15309
+ topLevelVal.scope,
15310
+ nextLevelType,
15311
+ nextLevelVal
15312
+ ));
15313
+ }
15314
+ }
15315
+ });
15316
+ return [coordinationScopes, coordinationScopesBy];
15317
+ }
15248
15318
  class VitessceConfigView {
15249
15319
  /**
15250
15320
  * Construct a new view instance.
@@ -15260,6 +15330,8 @@ class VitessceConfigView {
15260
15330
  this.view = {
15261
15331
  component,
15262
15332
  coordinationScopes,
15333
+ coordinationScopesBy: void 0,
15334
+ // TODO: initialize from parameter?
15263
15335
  x,
15264
15336
  y,
15265
15337
  w,
@@ -15279,6 +15351,41 @@ class VitessceConfigView {
15279
15351
  });
15280
15352
  return this;
15281
15353
  }
15354
+ useComplexCoordination(scopes) {
15355
+ if (!this.view.coordinationScopes) {
15356
+ this.view.coordinationScopes = {};
15357
+ }
15358
+ if (!this.view.coordinationScopesBy) {
15359
+ this.view.coordinationScopesBy = {};
15360
+ }
15361
+ const [nextCoordinationScopes, nextCoordinationScopesBy] = useComplexCoordinationHelper(
15362
+ scopes,
15363
+ this.view.coordinationScopes,
15364
+ this.view.coordinationScopesBy
15365
+ );
15366
+ this.view.coordinationScopes = nextCoordinationScopes;
15367
+ this.view.coordinationScopesBy = nextCoordinationScopesBy;
15368
+ return this;
15369
+ }
15370
+ /**
15371
+ * Attach meta coordination scopes to this view.
15372
+ * @param {VitessceConfigMetaCoordinationScope} metaScope A meta coordination scope instance.
15373
+ * @returns {VitessceConfigView} This, to allow chaining.
15374
+ */
15375
+ useMetaCoordination(metaScope) {
15376
+ if (!this.view.coordinationScopes) {
15377
+ this.view.coordinationScopes = {};
15378
+ }
15379
+ this.view.coordinationScopes[CoordinationType$1.META_COORDINATION_SCOPES] = [
15380
+ ...this.view.coordinationScopes[CoordinationType$1.META_COORDINATION_SCOPES] || [],
15381
+ metaScope.metaScope.cScope
15382
+ ];
15383
+ this.view.coordinationScopes[CoordinationType$1.META_COORDINATION_SCOPES_BY] = [
15384
+ ...this.view.coordinationScopes[CoordinationType$1.META_COORDINATION_SCOPES_BY] || [],
15385
+ metaScope.metaByScope.cScope
15386
+ ];
15387
+ return this;
15388
+ }
15282
15389
  /**
15283
15390
  * Set the x, y, w, h values for this view.
15284
15391
  * @param {number} x The x-coordinate of the view in the layout.
@@ -15330,6 +15437,11 @@ function vconcat(...views) {
15330
15437
  const vcvvc = new VitessceConfigViewVConcat(views);
15331
15438
  return vcvvc;
15332
15439
  }
15440
+ class CoordinationLevel {
15441
+ constructor(value) {
15442
+ this.value = value;
15443
+ }
15444
+ }
15333
15445
  class VitessceConfigCoordinationScope {
15334
15446
  /**
15335
15447
  * Construct a new coordination scope instance.
@@ -15351,6 +15463,63 @@ class VitessceConfigCoordinationScope {
15351
15463
  return this;
15352
15464
  }
15353
15465
  }
15466
+ class VitessceConfigMetaCoordinationScope {
15467
+ /**
15468
+ * Construct a new coordination scope instance.
15469
+ * @param {string} metaScope The name of the coordination scope for metaCoordinationScopes.
15470
+ * @param {string} metaByScope The name of the coordination scope for metaCoordinationScopesBy.
15471
+ */
15472
+ constructor(metaScope, metaByScope) {
15473
+ this.metaScope = new VitessceConfigCoordinationScope(
15474
+ CoordinationType$1.META_COORDINATION_SCOPES,
15475
+ metaScope
15476
+ );
15477
+ this.metaByScope = new VitessceConfigCoordinationScope(
15478
+ CoordinationType$1.META_COORDINATION_SCOPES_BY,
15479
+ metaByScope
15480
+ );
15481
+ }
15482
+ /**
15483
+ * Attach coordination scopes to this meta scope.
15484
+ * @param {...VitessceConfigCoordinationScope} args A variable number of
15485
+ * coordination scope instances.
15486
+ * @returns {VitessceConfigMetaCoordinationScope} This, to allow chaining.
15487
+ */
15488
+ useCoordination(...args) {
15489
+ const cScopes = args;
15490
+ const metaScopesVal = this.metaScope.cValue;
15491
+ cScopes.forEach((cScope) => {
15492
+ metaScopesVal[cScope.cType] = cScope.cScope;
15493
+ });
15494
+ this.metaScope.setValue(metaScopesVal);
15495
+ return this;
15496
+ }
15497
+ useComplexCoordination(scopes) {
15498
+ if (!this.metaScope.cValue) {
15499
+ this.metaScope.setValue({});
15500
+ }
15501
+ if (!this.metaByScope.cValue) {
15502
+ this.metaByScope.setValue({});
15503
+ }
15504
+ const [metaScopesVal, metaByScopesVal] = useComplexCoordinationHelper(
15505
+ scopes,
15506
+ this.metaScope.cValue,
15507
+ this.metaByScope.cValue
15508
+ );
15509
+ this.metaScope.setValue(metaScopesVal);
15510
+ this.metaByScope.setValue(metaByScopesVal);
15511
+ return this;
15512
+ }
15513
+ /**
15514
+ * Set the coordination value of the coordination scope.
15515
+ * @param {any} cValue The value to set.
15516
+ * @returns {VitessceConfigCoordinationScope} This, to allow chaining.
15517
+ */
15518
+ setValue(cValue) {
15519
+ this.cValue = cValue;
15520
+ return this;
15521
+ }
15522
+ }
15354
15523
  class VitessceConfig {
15355
15524
  /**
15356
15525
  * Construct a new view config instance.
@@ -15472,6 +15641,53 @@ class VitessceConfig {
15472
15641
  });
15473
15642
  return result;
15474
15643
  }
15644
+ addMetaCoordination() {
15645
+ const prevMetaScopes = this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES] ? Object.keys(this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES]) : [];
15646
+ const prevMetaByScopes = this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES_BY] ? Object.keys(this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES_BY]) : [];
15647
+ const metaContainer = new VitessceConfigMetaCoordinationScope(
15648
+ getNextScope(prevMetaScopes),
15649
+ getNextScope(prevMetaByScopes)
15650
+ );
15651
+ if (!this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES]) {
15652
+ this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES] = {};
15653
+ }
15654
+ if (!this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES_BY]) {
15655
+ this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES_BY] = {};
15656
+ }
15657
+ this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES][metaContainer.metaScope.cScope] = metaContainer.metaScope;
15658
+ this.config.coordinationSpace[CoordinationType$1.META_COORDINATION_SCOPES_BY][metaContainer.metaByScope.cScope] = metaContainer.metaByScope;
15659
+ return metaContainer;
15660
+ }
15661
+ addComplexCoordination(input) {
15662
+ const processLevel = (level) => {
15663
+ const result = {};
15664
+ Object.entries(level).forEach(([cType, nextLevelOrInitialValue]) => {
15665
+ if (nextLevelOrInitialValue instanceof CoordinationLevel) {
15666
+ const nextLevel = nextLevelOrInitialValue.value;
15667
+ if (Array.isArray(nextLevel)) {
15668
+ result[cType] = nextLevel.map((nextEl) => {
15669
+ const [dummyScope] = this.addCoordination(cType);
15670
+ dummyScope.setValue("__dummy__");
15671
+ return {
15672
+ scope: dummyScope,
15673
+ children: processLevel(nextEl)
15674
+ };
15675
+ });
15676
+ } else {
15677
+ throw new Error("Expected CoordinationLevel.value to be an array.");
15678
+ }
15679
+ } else {
15680
+ const initialValue = nextLevelOrInitialValue;
15681
+ const [scope] = this.addCoordination(cType);
15682
+ scope.setValue(initialValue);
15683
+ result[cType] = { scope };
15684
+ }
15685
+ });
15686
+ return result;
15687
+ };
15688
+ const output = processLevel(input);
15689
+ return output;
15690
+ }
15475
15691
  /**
15476
15692
  * A convenience function for setting up new coordination scopes across a set of views.
15477
15693
  * @param {VitessceConfigView[]} views An array of view objects to link together.
@@ -15584,6 +15800,109 @@ class VitessceConfig {
15584
15800
  return vc;
15585
15801
  }
15586
15802
  }
15803
+ const SINGLE_CELL_WITH_HEATMAP_VIEWS = {
15804
+ obsSets: { x: 4, y: 0, w: 4, h: 4 },
15805
+ obsSetSizes: { x: 8, y: 0, w: 4, h: 4 },
15806
+ scatterplot: { x: 0, y: 0, w: 4, h: 4 },
15807
+ heatmap: { x: 0, y: 4, w: 8, h: 4 },
15808
+ featureList: { x: 8, y: 4, w: 4, h: 4 }
15809
+ };
15810
+ const SINGLE_CELL_WITHOUT_HEATMAP_VIEWS = {
15811
+ obsSets: { x: 10, y: 6, w: 2, h: 6 },
15812
+ obsSetSizes: { x: 8, y: 1, w: 4, h: 6 },
15813
+ scatterplot: { x: 0, y: 0, w: 8, h: 12 },
15814
+ featureList: { x: 8, y: 6, w: 2, h: 6 }
15815
+ };
15816
+ const SPATIAL_TRANSCRIPTOMICS_VIEWS = {
15817
+ scatterplot: { x: 0, y: 0, w: 3, h: 4 },
15818
+ spatial: { x: 3, y: 0, w: 5, h: 4 },
15819
+ obsSets: { x: 8, y: 0, w: 4, h: 2 },
15820
+ featureList: { x: 8, y: 0, w: 4, h: 2 },
15821
+ heatmap: { x: 0, y: 4, w: 6, h: 4 },
15822
+ obsSetFeatureValueDistribution: { x: 6, y: 4, w: 6, h: 4 }
15823
+ };
15824
+ const SPATIAL_TRANSCRIPTOMICS_WITH_HSITOLOGY_VIEWS = {
15825
+ spatial: { x: 0, y: 0, w: 6, h: 6 },
15826
+ heatmap: { x: 0, y: 6, w: 8, h: 6 },
15827
+ layerController: { x: 8, y: 6, w: 4, h: 6 },
15828
+ obsSets: { x: 9, y: 0, w: 3, h: 6 },
15829
+ featureList: { x: 6, y: 0, w: 3, h: 6 }
15830
+ };
15831
+ const IMAGE_VIEWS = {
15832
+ spatial: { x: 0, y: 0, w: 8, h: 12 },
15833
+ layerController: { x: 8, y: 0, w: 4, h: 7 },
15834
+ description: { x: 8, y: 9, w: 4, h: 5 }
15835
+ };
15836
+ const NO_HINTS_CONFIG = {
15837
+ views: {},
15838
+ coordinationValues: {}
15839
+ };
15840
+ const HINTS_CONFIG = {
15841
+ "No hints are available. Generate config with no hints.": NO_HINTS_CONFIG,
15842
+ Basic: NO_HINTS_CONFIG,
15843
+ "Transcriptomics / scRNA-seq (with heatmap)": {
15844
+ views: SINGLE_CELL_WITH_HEATMAP_VIEWS
15845
+ },
15846
+ "Transcriptomics / scRNA-seq (without heatmap)": {
15847
+ views: SINGLE_CELL_WITHOUT_HEATMAP_VIEWS
15848
+ },
15849
+ "Spatial transcriptomics (with polygon cell segmentations)": {
15850
+ views: SPATIAL_TRANSCRIPTOMICS_VIEWS
15851
+ },
15852
+ "Chromatin accessibility / scATAC-seq (with heatmap)": {
15853
+ views: SINGLE_CELL_WITH_HEATMAP_VIEWS,
15854
+ coordinationValues: {
15855
+ featureType: "peak"
15856
+ }
15857
+ },
15858
+ "Chromatin accessibility / scATAC-seq (without heatmap)": {
15859
+ views: SINGLE_CELL_WITHOUT_HEATMAP_VIEWS,
15860
+ coordinationValues: {
15861
+ featureType: "peak"
15862
+ }
15863
+ },
15864
+ "Spatial transcriptomics (with histology image and polygon cell segmentations)": {
15865
+ views: SPATIAL_TRANSCRIPTOMICS_WITH_HSITOLOGY_VIEWS,
15866
+ coordinationSpaceRequired: true
15867
+ },
15868
+ Image: {
15869
+ views: IMAGE_VIEWS
15870
+ }
15871
+ };
15872
+ const HINT_TYPE_TO_FILE_TYPE_MAP = {
15873
+ "AnnData-Zarr": [
15874
+ "Basic",
15875
+ "Transcriptomics / scRNA-seq (with heatmap)",
15876
+ "Transcriptomics / scRNA-seq (without heatmap)",
15877
+ "Spatial transcriptomics (with polygon cell segmentations)",
15878
+ "Chromatin accessibility / scATAC-seq (with heatmap)",
15879
+ "Chromatin accessibility / scATAC-seq (without heatmap)"
15880
+ ],
15881
+ "OME-TIFF": [
15882
+ "Basic",
15883
+ "Image"
15884
+ ],
15885
+ "AnnData-Zarr,OME-TIFF": [
15886
+ "Basic",
15887
+ "Spatial transcriptomics (with histology image and polygon cell segmentations)"
15888
+ ]
15889
+ };
15890
+ const filterViews = (hintsConfig, possibleViews) => {
15891
+ const requiredViews = Object.keys(hintsConfig.views);
15892
+ if (requiredViews.length === 0) {
15893
+ return possibleViews;
15894
+ }
15895
+ const resultViews = [];
15896
+ requiredViews.forEach((requiredView) => {
15897
+ const match = possibleViews.find((possibleView) => possibleView[0] === requiredView);
15898
+ if (match)
15899
+ resultViews.push(match);
15900
+ });
15901
+ if (resultViews.length === 0) {
15902
+ throw new Error("No views found that are compatible with the supplied dataset URLs and hint.");
15903
+ }
15904
+ return resultViews;
15905
+ };
15587
15906
  class AbstractAutoConfig {
15588
15907
  async composeViewsConfig() {
15589
15908
  throw new Error("The composeViewsConfig() method has not been implemented.");
@@ -15599,12 +15918,11 @@ class OmeTiffAutoConfig extends AbstractAutoConfig {
15599
15918
  this.fileType = FileType$1.RASTER_JSON;
15600
15919
  this.fileName = fileUrl.split("/").at(-1);
15601
15920
  }
15602
- async composeViewsConfig() {
15603
- return [
15604
- ["description"],
15605
- ["spatial"],
15606
- ["layerController"]
15607
- ];
15921
+ async composeViewsConfig(hintsConfig) {
15922
+ return filterViews(
15923
+ hintsConfig,
15924
+ [["description"], ["spatial"], ["layerController"]]
15925
+ );
15608
15926
  }
15609
15927
  async composeFileConfig() {
15610
15928
  return {
@@ -15633,13 +15951,11 @@ class OmeZarrAutoConfig extends AbstractAutoConfig {
15633
15951
  this.fileType = FileType$1.RASTER_OME_ZARR;
15634
15952
  this.fileName = fileUrl.split("/").at(-1);
15635
15953
  }
15636
- async composeViewsConfig() {
15637
- return [
15638
- ["description"],
15639
- ["spatial"],
15640
- ["layerController"],
15641
- ["status"]
15642
- ];
15954
+ async composeViewsConfig(hintsConfig) {
15955
+ return filterViews(
15956
+ hintsConfig,
15957
+ [["description"], ["spatial"], ["layerController"]]
15958
+ );
15643
15959
  }
15644
15960
  async composeFileConfig() {
15645
15961
  return {
@@ -15658,6 +15974,7 @@ class AnndataZarrAutoConfig extends AbstractAutoConfig {
15658
15974
  this.metadataSummary = {};
15659
15975
  }
15660
15976
  async composeFileConfig() {
15977
+ var _a;
15661
15978
  this.metadataSummary = await this.setMetadataSummary();
15662
15979
  const options = {
15663
15980
  obsEmbedding: [],
@@ -15684,8 +16001,10 @@ class AnndataZarrAutoConfig extends AbstractAutoConfig {
15684
16001
  });
15685
16002
  const supportedObsSetsKeys = [
15686
16003
  "cluster",
16004
+ "clusters",
15687
16005
  "subcluster",
15688
16006
  "cell_type",
16007
+ "celltype",
15689
16008
  "leiden",
15690
16009
  "louvain",
15691
16010
  "disease",
@@ -15710,6 +16029,15 @@ class AnndataZarrAutoConfig extends AbstractAutoConfig {
15710
16029
  }
15711
16030
  });
15712
16031
  });
16032
+ options.obsSets = (_a = options.obsSets) == null ? void 0 : _a.map((obsSet) => {
16033
+ if (obsSet.path.length === 1) {
16034
+ return {
16035
+ ...obsSet,
16036
+ path: obsSet.path[0]
16037
+ };
16038
+ }
16039
+ return obsSet;
16040
+ });
15713
16041
  return {
15714
16042
  options,
15715
16043
  fileType: this.fileType,
@@ -15721,97 +16049,148 @@ class AnndataZarrAutoConfig extends AbstractAutoConfig {
15721
16049
  }
15722
16050
  };
15723
16051
  }
15724
- async composeViewsConfig() {
16052
+ async composeViewsConfig(hintsConfig) {
15725
16053
  this.metadataSummary = await this.setMetadataSummary();
15726
- const views = [];
15727
- const hasCellSetData = this.metadataSummary.obs.filter((key) => key.toLowerCase().includes("cluster") || key.toLowerCase().includes("cell_type"));
16054
+ const possibleViews = [];
16055
+ const hasCellSetData = this.metadataSummary.obs.filter((key) => key.toLowerCase().includes("cluster") || key.toLowerCase().includes("cell_type") || key.toLowerCase().includes("celltype"));
15728
16056
  if (hasCellSetData.length > 0) {
15729
- views.push(["obsSets"]);
16057
+ possibleViews.push(["obsSets"]);
15730
16058
  }
15731
16059
  this.metadataSummary.obsm.forEach((key) => {
15732
16060
  if (key.toLowerCase().includes("obsm/x_umap")) {
15733
- views.push(["scatterplot", { mapping: "UMAP" }]);
16061
+ possibleViews.push(["scatterplot", { mapping: "UMAP" }]);
15734
16062
  }
15735
16063
  if (key.toLowerCase().includes("obsm/x_tsne")) {
15736
- views.push(["scatterplot", { mapping: "t-SNE" }]);
16064
+ possibleViews.push(["scatterplot", { mapping: "t-SNE" }]);
15737
16065
  }
15738
16066
  if (key.toLowerCase().includes("obsm/x_pca")) {
15739
- views.push(["scatterplot", { mapping: "PCA" }]);
16067
+ possibleViews.push(["scatterplot", { mapping: "PCA" }]);
15740
16068
  }
15741
16069
  if (key.toLowerCase().includes("obsm/x_segmentations")) {
15742
- views.push(["layerController"]);
16070
+ possibleViews.push(["layerController"]);
15743
16071
  }
15744
16072
  if (key.toLowerCase().includes("obsm/x_spatial")) {
15745
- views.push(["spatial"]);
16073
+ possibleViews.push(["spatial"]);
15746
16074
  }
15747
16075
  });
16076
+ possibleViews.push(["obsSetSizes"]);
16077
+ possibleViews.push(["obsSetFeatureValueDistribution"]);
15748
16078
  if (this.metadataSummary.X) {
15749
- views.push(["heatmap"]);
15750
- views.push(["featureList"]);
16079
+ possibleViews.push(["heatmap"]);
16080
+ possibleViews.push(["featureList"]);
15751
16081
  }
16082
+ const views = filterViews(hintsConfig, possibleViews);
15752
16083
  return views;
15753
16084
  }
16085
+ async setMetadataSummaryWithZmetadata(response) {
16086
+ const metadataFile = await response.json();
16087
+ if (!metadataFile.metadata) {
16088
+ throw new Error("Could not generate config: .zmetadata file is not valid.");
16089
+ }
16090
+ const obsmKeys = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obsm/X_")).map((key) => key.split("/.zarray")[0]);
16091
+ const obsKeysArr = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obs/")).map((key) => key.split("/.za")[0]);
16092
+ function uniq(a) {
16093
+ return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
16094
+ }
16095
+ const obsKeys = uniq(obsKeysArr);
16096
+ const X = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("X"));
16097
+ return {
16098
+ // Array of keys in obsm that are found by the fetches above
16099
+ obsm: obsmKeys,
16100
+ // Array of keys in obs that are found by the fetches above
16101
+ obs: obsKeys,
16102
+ // Boolean indicating whether the X array was found by the fetches above
16103
+ X: X.length > 0
16104
+ };
16105
+ }
16106
+ async setMetadataSummaryWithoutZmetadata() {
16107
+ const knownMetadataFileSuffixes = [
16108
+ "/obsm/X_pca/.zarray",
16109
+ "/obsm/X_umap/.zarray",
16110
+ "/obsm/X_tsne/.zarray",
16111
+ "/obsm/X_spatial/.zarray",
16112
+ "/obsm/X_segmentations/.zarray",
16113
+ "/obs/.zattrs",
16114
+ "/X/.zarray",
16115
+ "/X/data/.zarray"
16116
+ // for https://s3.amazonaws.com/vitessce-data/0.0.33/main/human-lymph-node-10x-visium/human_lymph_node_10x_visium.h5ad.zarr
16117
+ ];
16118
+ const getObsmKey = (url) => {
16119
+ const obsmKeyStartIndex = `${this.fileUrl}/`.length;
16120
+ const obsmKeyEndIndex = url.length - "/.zarray".length;
16121
+ return url.substring(obsmKeyStartIndex, obsmKeyEndIndex);
16122
+ };
16123
+ const promises = knownMetadataFileSuffixes.map((suffix) => fetch(`${this.fileUrl}${suffix}`));
16124
+ const fetchResults = await Promise.all(promises);
16125
+ const okFetchResults = fetchResults.filter((j) => j.ok);
16126
+ const metadataSummary = {
16127
+ // Array of keys in obsm that are found by the fetches above
16128
+ obsm: [],
16129
+ // Array of keys in obs that are found by the fetches above
16130
+ obs: [],
16131
+ // Boolean indicating whether the X array was found by the fetches above
16132
+ X: false
16133
+ };
16134
+ const obsPromiseResult = okFetchResults.find(
16135
+ (r) => r.url === `${this.fileUrl}/obs/.zattrs`
16136
+ );
16137
+ const isObsValid = (obsAttr) => Object.keys(obsAttr).includes("column-order") && Object.keys(obsAttr).includes("encoding-version") && Object.keys(obsAttr).includes("encoding-type") && obsAttr["encoding-type"] === "dataframe" && (obsAttr["encoding-version"] === "0.1.0" || obsAttr["encoding-version"] === "0.2.0");
16138
+ if (obsPromiseResult) {
16139
+ const obsAttrs = await obsPromiseResult.json();
16140
+ if (isObsValid(obsAttrs)) {
16141
+ obsAttrs["column-order"].forEach((key) => metadataSummary.obs.push(`obs/${key}`));
16142
+ } else {
16143
+ throw new Error("Could not generate config: /obs/.zattrs file is not valid.");
16144
+ }
16145
+ }
16146
+ okFetchResults.forEach((r) => {
16147
+ if (r.url.startsWith(`${this.fileUrl}/obsm`)) {
16148
+ const obsmKey = getObsmKey(r.url);
16149
+ if (obsmKey) {
16150
+ metadataSummary.obsm.push(obsmKey);
16151
+ }
16152
+ } else if (r.url.startsWith(`${this.fileUrl}/X`)) {
16153
+ metadataSummary.X = true;
16154
+ }
16155
+ });
16156
+ return metadataSummary;
16157
+ }
15754
16158
  async setMetadataSummary() {
15755
16159
  if (Object.keys(this.metadataSummary).length > 0) {
15756
16160
  return this.metadataSummary;
15757
16161
  }
15758
- const parseMetadataFile = (metadataFile) => {
15759
- if (!metadataFile.metadata) {
15760
- throw new Error("Could not generate config: .zmetadata file is not valid.");
15761
- }
15762
- const obsmKeys = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obsm/X_")).map((key) => key.split("/.zarray")[0]);
15763
- const obsKeysArr = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("obs/")).map((key) => key.split("/.za")[0]);
15764
- function uniq(a) {
15765
- return a.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
15766
- }
15767
- const obsKeys = uniq(obsKeysArr);
15768
- const X = Object.keys(metadataFile.metadata).filter((key) => key.startsWith("X"));
15769
- const out = {
15770
- obsm: obsmKeys,
15771
- obs: obsKeys,
15772
- X: X.length > 0
15773
- };
15774
- return out;
15775
- };
15776
16162
  const metadataExtension = ".zmetadata";
15777
16163
  const url = [this.fileUrl, metadataExtension].join("/");
15778
16164
  return fetch(url).then((response) => {
15779
16165
  if (response.ok) {
15780
- return response.json();
16166
+ return this.setMetadataSummaryWithZmetadata(response);
15781
16167
  }
15782
- return Promise.reject(response);
15783
- }).then((responseJson) => parseMetadataFile(responseJson)).catch((error) => {
15784
- if (error.status === 404) {
15785
- const errorMssg = `Could not generate config. File ${metadataExtension} not found in supplied file URL. Check docs for more explanation.`;
15786
- return Promise.reject(new Error(errorMssg));
16168
+ if (response.status === 404) {
16169
+ return this.setMetadataSummaryWithoutZmetadata();
15787
16170
  }
15788
- return Promise.reject(error);
16171
+ throw new Error(`Could not generate config: ${response.statusText}`);
16172
+ }).catch((error) => {
16173
+ throw new Error(`Could not generate config for URL ${this.fileUrl}: ${error}`);
15789
16174
  });
15790
16175
  }
15791
16176
  }
15792
16177
  const configClasses = [
15793
16178
  {
15794
16179
  extensions: [".ome.tif", ".ome.tiff", ".ome.tf2", ".ome.tf8"],
15795
- class: OmeTiffAutoConfig
16180
+ class: OmeTiffAutoConfig,
16181
+ name: "OME-TIFF"
15796
16182
  },
15797
16183
  {
15798
16184
  extensions: [".h5ad.zarr", ".adata.zarr", ".anndata.zarr"],
15799
- class: AnndataZarrAutoConfig
16185
+ class: AnndataZarrAutoConfig,
16186
+ name: "AnnData-Zarr"
15800
16187
  },
15801
16188
  {
15802
16189
  extensions: ["ome.zarr"],
15803
- class: OmeZarrAutoConfig
16190
+ class: OmeZarrAutoConfig,
16191
+ name: "OME-Zarr"
15804
16192
  }
15805
16193
  ];
15806
- function getFileType(url) {
15807
- const match = configClasses.find((obj) => obj.extensions.filter(
15808
- (ext) => url.endsWith(ext)
15809
- ).length === 1);
15810
- if (!match) {
15811
- throw new Error(`Could not generate config for URL: ${url}. This file type is not supported.`);
15812
- }
15813
- return match.class;
15814
- }
15815
16194
  function calculateCoordinates(viewsNumb) {
15816
16195
  const rows = Math.ceil(Math.sqrt(viewsNumb));
15817
16196
  const cols = Math.ceil(viewsNumb / rows);
@@ -15823,14 +16202,117 @@ function calculateCoordinates(viewsNumb) {
15823
16202
  const col = i % cols;
15824
16203
  const x = col * width;
15825
16204
  const y = row * height;
15826
- coords.push([x, y, width, height]);
16205
+ coords.push([
16206
+ Math.floor(x),
16207
+ Math.floor(y),
16208
+ // Ensure width/height is at least 1.
16209
+ Math.max(1, Math.floor(width)),
16210
+ Math.max(1, Math.floor(height))
16211
+ ]);
15827
16212
  }
15828
16213
  return coords;
15829
16214
  }
15830
- async function generateConfig(url, vc) {
16215
+ const spatialSegmentationLayerValue = {
16216
+ radius: 65,
16217
+ stroked: true,
16218
+ visible: true,
16219
+ opacity: 1
16220
+ };
16221
+ function insertCoordinationSpaceForSpatial(views, vc) {
16222
+ const [
16223
+ spatialSegmentationLayer,
16224
+ spatialImageLayer,
16225
+ spatialZoom,
16226
+ spatialTargetX,
16227
+ spatialTargetY
16228
+ ] = vc.addCoordination(
16229
+ CoordinationType$1.SPATIAL_SEGMENTATION_LAYER,
16230
+ CoordinationType$1.SPATIAL_IMAGE_LAYER,
16231
+ CoordinationType$1.SPATIAL_ZOOM,
16232
+ CoordinationType$1.SPATIAL_TARGET_X,
16233
+ CoordinationType$1.SPATIAL_TARGET_Y
16234
+ );
16235
+ spatialSegmentationLayer.setValue(spatialSegmentationLayerValue);
16236
+ spatialImageLayer.setValue([
16237
+ {
16238
+ type: "raster",
16239
+ index: 0,
16240
+ colormap: null,
16241
+ transparentColor: null,
16242
+ opacity: 1,
16243
+ domainType: "Min/Max",
16244
+ channels: [
16245
+ {
16246
+ selection: {
16247
+ c: 0
16248
+ },
16249
+ color: [
16250
+ 255,
16251
+ 0,
16252
+ 0
16253
+ ],
16254
+ visible: true,
16255
+ slider: [
16256
+ 0,
16257
+ 255
16258
+ ]
16259
+ },
16260
+ {
16261
+ selection: {
16262
+ c: 1
16263
+ },
16264
+ color: [
16265
+ 0,
16266
+ 255,
16267
+ 0
16268
+ ],
16269
+ visible: true,
16270
+ slider: [
16271
+ 0,
16272
+ 255
16273
+ ]
16274
+ },
16275
+ {
16276
+ selection: {
16277
+ c: 2
16278
+ },
16279
+ color: [
16280
+ 0,
16281
+ 0,
16282
+ 255
16283
+ ],
16284
+ visible: true,
16285
+ slider: [
16286
+ 0,
16287
+ 255
16288
+ ]
16289
+ }
16290
+ ]
16291
+ }
16292
+ ]);
16293
+ views.forEach((view) => {
16294
+ if (view.view.component === "spatial" || view.view.component === "layerController") {
16295
+ view.useCoordination(spatialImageLayer);
16296
+ view.useCoordination(spatialSegmentationLayer);
16297
+ view.useCoordination(spatialZoom);
16298
+ view.useCoordination(spatialTargetX);
16299
+ view.useCoordination(spatialTargetY);
16300
+ }
16301
+ });
16302
+ }
16303
+ function getFileType(url) {
16304
+ const match = configClasses.find((obj) => obj.extensions.filter(
16305
+ (ext) => url.endsWith(ext)
16306
+ ).length === 1);
16307
+ if (!match) {
16308
+ throw new Error("One or more of the URLs provided point to unsupported file types.");
16309
+ }
16310
+ return match;
16311
+ }
16312
+ async function generateViewDefinition(url, vc, dataset, hintsConfig) {
15831
16313
  let ConfigClassName;
15832
16314
  try {
15833
- ConfigClassName = getFileType(url);
16315
+ ConfigClassName = getFileType(url).class;
15834
16316
  } catch (err) {
15835
16317
  return Promise.reject(err);
15836
16318
  }
@@ -15839,12 +16321,12 @@ async function generateConfig(url, vc) {
15839
16321
  let viewsConfig;
15840
16322
  try {
15841
16323
  fileConfig = await configInstance.composeFileConfig();
15842
- viewsConfig = await configInstance.composeViewsConfig();
16324
+ viewsConfig = await configInstance.composeViewsConfig(hintsConfig);
15843
16325
  } catch (error) {
15844
16326
  console.error(error);
15845
16327
  return Promise.reject(error);
15846
16328
  }
15847
- const dataset = vc.addDataset(configInstance.fileName).addFile(fileConfig);
16329
+ dataset.addFile(fileConfig);
15848
16330
  let layerControllerView = false;
15849
16331
  let spatialView = false;
15850
16332
  const views = [];
@@ -15856,7 +16338,7 @@ async function generateConfig(url, vc) {
15856
16338
  if (v[0] === "spatial") {
15857
16339
  spatialView = view;
15858
16340
  }
15859
- if (v[0] === "layerController" && configInstance instanceof OmeTiffAutoConfig) {
16341
+ if (v[0] === "layerController") {
15860
16342
  view.setProps({
15861
16343
  disable3d: [],
15862
16344
  disableChannelsIfRgbDetected: true
@@ -15868,47 +16350,71 @@ async function generateConfig(url, vc) {
15868
16350
  views.push(view);
15869
16351
  });
15870
16352
  if (layerControllerView && spatialView && configInstance instanceof AnndataZarrAutoConfig) {
15871
- const spatialSegmentationLayerValue = {
15872
- opacity: 1,
15873
- radius: 0,
15874
- visible: true,
15875
- stroked: false
15876
- };
15877
16353
  vc.linkViews(
15878
16354
  [spatialView, layerControllerView],
15879
16355
  [
15880
- CoordinationType$1.SPATIAL_ZOOM,
15881
- CoordinationType$1.SPATIAL_TARGET_X,
15882
- CoordinationType$1.SPATIAL_TARGET_Y,
15883
16356
  CoordinationType$1.SPATIAL_SEGMENTATION_LAYER
15884
16357
  ],
15885
- [-5.5, 16e3, 2e4, spatialSegmentationLayerValue]
16358
+ [spatialSegmentationLayerValue]
15886
16359
  );
15887
16360
  }
15888
16361
  return views;
15889
16362
  }
15890
- async function generateConfigs(fileUrls) {
16363
+ function getHintOptions(fileUrls) {
16364
+ const fileTypes = {};
16365
+ fileUrls.forEach((url) => {
16366
+ const match = getFileType(url);
16367
+ if (match.name === "OME-Zarr") {
16368
+ fileTypes["OME-TIFF"] = true;
16369
+ } else {
16370
+ fileTypes[match.name] = true;
16371
+ }
16372
+ });
16373
+ const datasetType = Object.keys(fileTypes).sort().join(",");
16374
+ return (HINT_TYPE_TO_FILE_TYPE_MAP == null ? void 0 : HINT_TYPE_TO_FILE_TYPE_MAP[datasetType]) || [];
16375
+ }
16376
+ async function generateConfig(fileUrls, hintTitle = null) {
16377
+ var _a;
15891
16378
  const vc = new VitessceConfig({
15892
16379
  schemaVersion: "1.0.15",
15893
16380
  name: "An automatically generated config. Adjust values and add layout components if needed.",
15894
16381
  description: "Populate with text relevant to this visualisation."
15895
16382
  });
15896
16383
  const allViews = [];
16384
+ const dataset = vc.addDataset("An automatically generated view config for dataset. Adjust values and add layout components if needed.");
16385
+ const hintsConfig = !hintTitle ? { views: {} } : HINTS_CONFIG == null ? void 0 : HINTS_CONFIG[hintTitle];
16386
+ if (!hintsConfig) {
16387
+ throw new Error(`Hints config not found for the supplied hint: ${hintTitle}.`);
16388
+ }
16389
+ const useHints = ((_a = Object.keys(hintsConfig == null ? void 0 : hintsConfig.views)) == null ? void 0 : _a.length) > 0;
15897
16390
  fileUrls.forEach((url) => {
15898
- allViews.push(generateConfig(url, vc));
16391
+ allViews.push(generateViewDefinition(url, vc, dataset, hintsConfig));
15899
16392
  });
15900
16393
  return Promise.all(allViews).then((views) => {
15901
16394
  const flattenedViews = views.flat();
15902
- const coord = calculateCoordinates(flattenedViews.length);
15903
- for (let i = 0; i < flattenedViews.length; i++) {
15904
- flattenedViews[i].setXYWH(...coord[i]);
16395
+ if (hintsConfig == null ? void 0 : hintsConfig.coordinationSpaceRequired) {
16396
+ insertCoordinationSpaceForSpatial(flattenedViews, vc);
16397
+ }
16398
+ if (!useHints) {
16399
+ const coord = calculateCoordinates(flattenedViews.length);
16400
+ for (let i = 0; i < flattenedViews.length; i++) {
16401
+ flattenedViews[i].setXYWH(...coord[i]);
16402
+ }
16403
+ } else {
16404
+ flattenedViews.forEach((vitessceConfigView) => {
16405
+ const coordinates = Object.values(hintsConfig.views[vitessceConfigView.view.component]);
16406
+ vitessceConfigView.setXYWH(...coordinates);
16407
+ });
15905
16408
  }
15906
16409
  return vc.toJSON();
15907
16410
  });
15908
16411
  }
15909
16412
  export {
16413
+ HINTS_CONFIG,
16414
+ HINT_TYPE_TO_FILE_TYPE_MAP,
15910
16415
  VitessceConfig,
15911
- generateConfigs,
16416
+ generateConfig,
16417
+ getHintOptions,
15912
16418
  hconcat,
15913
16419
  vconcat
15914
16420
  };