@vitessce/config 3.1.2 → 3.2.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.
@@ -102,7 +102,7 @@ export class VitessceConfigDataset {
102
102
  }
103
103
  }
104
104
 
105
- function useComplexCoordinationHelper(scopes, coordinationScopes, coordinationScopesBy) {
105
+ function useCoordinationByObjectHelper(scopes, coordinationScopes, coordinationScopesBy) {
106
106
  // Set this.coordinationScopes and this.coordinationScopesBy by recursion on `scopes`.
107
107
  /*
108
108
  // Destructured, `scopes` might look like:
@@ -111,7 +111,7 @@ function useComplexCoordinationHelper(scopes, coordinationScopes, coordinationSc
111
111
  {
112
112
  scope: imageLayerScope,
113
113
  children: {
114
- [CoordinationType.IMAGE]: { scope: imageScope },
114
+ [CoordinationType.FILE_UID]: { scope: imageScope },
115
115
  [CoordinationType.SPATIAL_LAYER_VISIBLE]: { scope: imageVisibleScope },
116
116
  [CoordinationType.SPATIAL_LAYER_OPACITY]: { scope: imageOpacityScope },
117
117
  [CoordinationType.SPATIAL_IMAGE_CHANNEL]: [
@@ -143,7 +143,7 @@ function useComplexCoordinationHelper(scopes, coordinationScopes, coordinationSc
143
143
  };
144
144
  this.coordinationScopesBy = {
145
145
  [CoordinationType.SPATIAL_IMAGE_LAYER]: {
146
- [CoordinationType.IMAGE]: {
146
+ [CoordinationType.FILE_UID]: {
147
147
  [imageLayerScope.cScope]: imageScope.cScope,
148
148
  },
149
149
  [CoordinationType.SPATIAL_LAYER_VISIBLE]: {
@@ -278,14 +278,20 @@ export class VitessceConfigView {
278
278
  return this;
279
279
  }
280
280
 
281
- useComplexCoordination(scopes) {
281
+ /**
282
+ * Attach potentially multi-level coordination scopes to this view.
283
+ * @param {object} scopes A value returned by `VitessceConfig.addCoordinationByObject`.
284
+ * Not intended to be a manually-constructed object.
285
+ * @returns {VitessceConfigView} This, to allow chaining.
286
+ */
287
+ useCoordinationByObject(scopes) {
282
288
  if (!this.view.coordinationScopes) {
283
289
  this.view.coordinationScopes = {};
284
290
  }
285
291
  if (!this.view.coordinationScopesBy) {
286
292
  this.view.coordinationScopesBy = {};
287
293
  }
288
- const [nextCoordinationScopes, nextCoordinationScopesBy] = useComplexCoordinationHelper(
294
+ const [nextCoordinationScopes, nextCoordinationScopesBy] = useCoordinationByObjectHelper(
289
295
  scopes,
290
296
  this.view.coordinationScopes,
291
297
  this.view.coordinationScopesBy,
@@ -396,6 +402,19 @@ export function vconcat(...views) {
396
402
  class CoordinationLevel {
397
403
  constructor(value) {
398
404
  this.value = value;
405
+ this.cachedValue = null;
406
+ }
407
+
408
+ setCached(processedLevel) {
409
+ this.cachedValue = processedLevel;
410
+ }
411
+
412
+ getCached() {
413
+ return this.cachedValue;
414
+ }
415
+
416
+ isCached() {
417
+ return this.cachedValue !== null;
399
418
  }
400
419
  }
401
420
 
@@ -467,14 +486,21 @@ export class VitessceConfigMetaCoordinationScope {
467
486
  return this;
468
487
  }
469
488
 
470
- useComplexCoordination(scopes) {
489
+ /**
490
+ * Attach potentially multi-level coordination scopes to this meta coordination
491
+ * scope instance.
492
+ * @param {object} scopes A value returned by `VitessceConfig.addCoordinationByObject`.
493
+ * Not intended to be a manually-constructed object.
494
+ * @returns {VitessceConfigView} This, to allow chaining.
495
+ */
496
+ useCoordinationByObject(scopes) {
471
497
  if (!this.metaScope.cValue) {
472
498
  this.metaScope.setValue({});
473
499
  }
474
500
  if (!this.metaByScope.cValue) {
475
501
  this.metaByScope.setValue({});
476
502
  }
477
- const [metaScopesVal, metaByScopesVal] = useComplexCoordinationHelper(
503
+ const [metaScopesVal, metaByScopesVal] = useCoordinationByObjectHelper(
478
504
  scopes,
479
505
  this.metaScope.cValue,
480
506
  this.metaByScope.cValue,
@@ -483,16 +509,6 @@ export class VitessceConfigMetaCoordinationScope {
483
509
  this.metaByScope.setValue(metaByScopesVal);
484
510
  return this;
485
511
  }
486
-
487
- /**
488
- * Set the coordination value of the coordination scope.
489
- * @param {any} cValue The value to set.
490
- * @returns {VitessceConfigCoordinationScope} This, to allow chaining.
491
- */
492
- setValue(cValue) {
493
- this.cValue = cValue;
494
- return this;
495
- }
496
512
  }
497
513
 
498
514
  /**
@@ -635,6 +651,11 @@ export class VitessceConfig {
635
651
  return result;
636
652
  }
637
653
 
654
+ /**
655
+ * Initialize a new meta coordination scope in the coordination space,
656
+ * and get a reference to it in the form of a meta coordination scope instance.
657
+ * @returns {VitessceConfigMetaCoordinationScope} A new meta coordination scope instance.
658
+ */
638
659
  addMetaCoordination() {
639
660
  const prevMetaScopes = (
640
661
  this.config.coordinationSpace[CoordinationType.META_COORDINATION_SCOPES]
@@ -663,13 +684,26 @@ export class VitessceConfig {
663
684
  return metaContainer;
664
685
  }
665
686
 
666
- addComplexCoordination(input) {
687
+ /**
688
+ * Set up the initial values for multi-level coordination in the coordination space.
689
+ * Get a reference to these values to pass to the `useCoordinationByObject` method
690
+ * of either view or meta coordination scope instances.
691
+ * @param {object} input A (potentially nested) object with coordination types as keys
692
+ * and values being either the initial coordination value, a `VitessceConfigCoordinationScope`
693
+ * instance, or a `CoordinationLevel` instance.
694
+ * The CL function takes an array of objects as its argument, and returns a CoordinationLevel
695
+ * instance, to support nesting.
696
+ * @returns {object} A (potentially nested) object with coordination types as keys and values
697
+ * being either { scope }, { scope, children }, or an array of these. Not intended to be
698
+ * manipulated before being passed to a `useCoordinationByObject` function.
699
+ */
700
+ addCoordinationByObject(input) {
667
701
  /*
668
702
  // The value for `input` might look like:
669
703
  {
670
704
  [CoordinationType.SPATIAL_IMAGE_LAYER]: CL([
671
705
  {
672
- [CoordinationType.IMAGE]: 'S-1905-017737_bf',
706
+ [CoordinationType.FILE_UID]: 'S-1905-017737_bf',
673
707
  [CoordinationType.SPATIAL_LAYER_VISIBLE]: true,
674
708
  [CoordinationType.SPATIAL_LAYER_OPACITY]: 1,
675
709
  [CoordinationType.SPATIAL_IMAGE_CHANNEL]: CL([
@@ -686,7 +720,7 @@ export class VitessceConfig {
686
720
  ]),
687
721
  [CoordinationType.SPATIAL_SEGMENTATION_LAYER]: CL([
688
722
  {
689
- [CoordinationType.IMAGE]: 'S-1905-017737',
723
+ [CoordinationType.FILE_UID]: 'S-1905-017737',
690
724
  [CoordinationType.SPATIAL_LAYER_VISIBLE]: true,
691
725
  [CoordinationType.SPATIAL_LAYER_OPACITY]: 1,
692
726
  [CoordinationType.SPATIAL_SEGMENTATION_CHANNEL]: CL([
@@ -710,13 +744,13 @@ export class VitessceConfig {
710
744
  ]),
711
745
  }
712
746
  // Which would correspond to this `output`,
713
- // a valid input for `VitessceConfigMetaCoordinationScope.useComplexCoordination()`:
747
+ // a valid input for `VitessceConfigMetaCoordinationScope.useCoordinationByObject()`:
714
748
  {
715
749
  [CoordinationType.SPATIAL_IMAGE_LAYER]: [
716
750
  {
717
751
  scope: imageLayerScope,
718
752
  children: {
719
- [CoordinationType.IMAGE]: { scope: imageScope },
753
+ [CoordinationType.FILE_UID]: { scope: imageScope },
720
754
  [CoordinationType.SPATIAL_LAYER_VISIBLE]: { scope: imageVisibleScope },
721
755
  [CoordinationType.SPATIAL_LAYER_OPACITY]: { scope: imageOpacityScope },
722
756
  [CoordinationType.SPATIAL_IMAGE_CHANNEL]: [
@@ -748,8 +782,10 @@ export class VitessceConfig {
748
782
  // (otherwise assume it is the coordination value).
749
783
  if (nextLevelOrInitialValue instanceof CoordinationLevel) {
750
784
  const nextLevel = nextLevelOrInitialValue.value;
751
- if (Array.isArray(nextLevel)) {
752
- result[cType] = nextLevel.map((nextEl) => {
785
+ if (nextLevelOrInitialValue.isCached()) {
786
+ result[cType] = nextLevelOrInitialValue.getCached();
787
+ } else if (Array.isArray(nextLevel)) {
788
+ const processedLevel = nextLevel.map((nextEl) => {
753
789
  const [dummyScope] = this.addCoordination(cType);
754
790
  // TODO: set a better initial value for dummy cases.
755
791
  dummyScope.setValue('__dummy__');
@@ -758,15 +794,30 @@ export class VitessceConfig {
758
794
  children: processLevel(nextEl),
759
795
  };
760
796
  });
797
+ nextLevelOrInitialValue.setCached(processedLevel);
798
+ result[cType] = processedLevel;
761
799
  } else {
762
- throw new Error('Expected CoordinationLevel.value to be an array.');
800
+ const nextEl = nextLevel;
801
+ const [dummyScope] = this.addCoordination(cType);
802
+ // TODO: set a better initial value for dummy cases.
803
+ dummyScope.setValue('__dummy__');
804
+ const processedLevel = {
805
+ scope: dummyScope,
806
+ children: processLevel(nextEl),
807
+ };
808
+ nextLevelOrInitialValue.setCached(processedLevel);
809
+ result[cType] = processedLevel;
763
810
  }
764
811
  } else {
765
812
  // Base case.
766
813
  const initialValue = nextLevelOrInitialValue;
767
- const [scope] = this.addCoordination(cType);
768
- scope.setValue(initialValue);
769
- result[cType] = { scope };
814
+ if (initialValue instanceof VitessceConfigCoordinationScope) {
815
+ result[cType] = { scope: initialValue };
816
+ } else {
817
+ const [scope] = this.addCoordination(cType);
818
+ scope.setValue(initialValue);
819
+ result[cType] = { scope };
820
+ }
770
821
  }
771
822
  });
772
823
  return result;
@@ -799,6 +850,35 @@ export class VitessceConfig {
799
850
  return this;
800
851
  }
801
852
 
853
+ /**
854
+ * A convenience function for setting up multi-level and meta-coordination scopes
855
+ * across a set of views.
856
+ * @param {VitessceConfigView[]} views An array of view objects to link together.
857
+ * @param {object} input A (potentially nested) object with coordination types as keys
858
+ * and values being either the initial coordination value, a `VitessceConfigCoordinationScope`
859
+ * instance, or a `CoordinationLevel` instance.
860
+ * The CL function takes an array of objects as its argument, and returns a CoordinationLevel
861
+ * instance, to support nesting.
862
+ * @param {boolean} meta Should meta-coordination be used? Optional. By default, true.
863
+ * @returns {VitessceConfig} This, to allow chaining.
864
+ */
865
+ linkViewsByObject(views, input, meta = true) {
866
+ const scopes = this.addCoordinationByObject(input);
867
+ if (meta) {
868
+ const metaScope = this.addMetaCoordination();
869
+ metaScope.useCoordinationByObject(scopes);
870
+
871
+ views.forEach((view) => {
872
+ view.useMetaCoordination(metaScope);
873
+ });
874
+ } else {
875
+ views.forEach((view) => {
876
+ view.useCoordinationByObject(scopes);
877
+ });
878
+ }
879
+ return this;
880
+ }
881
+
802
882
  /**
803
883
  * Set the layout of views.
804
884
  * @param {VitessceConfigView|VitessceConfigViewHConcat|VitessceConfigViewVConcat} viewConcat A
@@ -269,7 +269,7 @@ describe('src/api/VitessceConfig.js', () => {
269
269
  schemaVersion: '1.0.16',
270
270
  name: 'My config',
271
271
  });
272
- config.addComplexCoordination({
272
+ config.addCoordinationByObject({
273
273
  spatialImageLayer: CL([
274
274
  {
275
275
  image: 'S-1905-017737_bf',
@@ -342,7 +342,14 @@ describe('src/api/VitessceConfig.js', () => {
342
342
  });
343
343
  const dataset = config.addDataset('My dataset');
344
344
 
345
- const scopes = config.addComplexCoordination({
345
+ // Coordinate all segmentation channels on the same color,
346
+ // to test out the use of a coordination scope instance as a value.
347
+ const [colorScope] = config.addCoordination(
348
+ 'spatialChannelColor',
349
+ );
350
+ colorScope.setValue([255, 0, 0]);
351
+
352
+ const scopes = config.addCoordinationByObject({
346
353
  spatialImageLayer: CL([
347
354
  {
348
355
  image: 'S-1905-017737_bf',
@@ -351,11 +358,11 @@ describe('src/api/VitessceConfig.js', () => {
351
358
  spatialImageChannel: CL([
352
359
  {
353
360
  spatialTargetC: 0,
354
- spatialChannelColor: [255, 0, 0],
361
+ spatialChannelColor: [0, 255, 0],
355
362
  },
356
363
  {
357
364
  spatialTargetC: 1,
358
- spatialChannelColor: [0, 255, 0],
365
+ spatialChannelColor: [0, 0, 255],
359
366
  },
360
367
  ]),
361
368
  },
@@ -369,17 +376,17 @@ describe('src/api/VitessceConfig.js', () => {
369
376
  {
370
377
  obsType: 'Cortical Interstitia',
371
378
  spatialTargetC: 0,
372
- spatialChannelColor: [255, 0, 0],
379
+ spatialChannelColor: colorScope,
373
380
  },
374
381
  {
375
382
  obsType: 'Non-Globally Sclerotic Glomeruli',
376
383
  spatialTargetC: 1,
377
- spatialChannelColor: [255, 0, 0],
384
+ spatialChannelColor: colorScope,
378
385
  },
379
386
  {
380
387
  obsType: 'Globally Sclerotic Glomeruli',
381
388
  spatialTargetC: 2,
382
- spatialChannelColor: [255, 0, 0],
389
+ spatialChannelColor: colorScope,
383
390
  },
384
391
  ]),
385
392
  },
@@ -387,7 +394,7 @@ describe('src/api/VitessceConfig.js', () => {
387
394
  });
388
395
 
389
396
  const spatialView = config.addView(dataset, 'spatial');
390
- spatialView.useComplexCoordination(scopes);
397
+ spatialView.useCoordinationByObject(scopes);
391
398
 
392
399
  const configJSON = config.toJSON();
393
400
  expect(configJSON).toEqual({
@@ -407,9 +414,7 @@ describe('src/api/VitessceConfig.js', () => {
407
414
  spatialChannelColor: {
408
415
  A: [255, 0, 0],
409
416
  B: [0, 255, 0],
410
- C: [255, 0, 0],
411
- D: [255, 0, 0],
412
- E: [255, 0, 0],
417
+ C: [0, 0, 255],
413
418
  },
414
419
  spatialSegmentationLayer: { A: '__dummy__' },
415
420
  spatialSegmentationChannel: { A: '__dummy__', B: '__dummy__', C: '__dummy__' },
@@ -435,7 +440,7 @@ describe('src/api/VitessceConfig.js', () => {
435
440
  },
436
441
  spatialImageChannel: {
437
442
  spatialTargetC: { A: 'A', B: 'B' },
438
- spatialChannelColor: { A: 'A', B: 'B' },
443
+ spatialChannelColor: { A: 'B', B: 'C' },
439
444
  },
440
445
  spatialSegmentationLayer: {
441
446
  image: { A: 'B' },
@@ -446,7 +451,7 @@ describe('src/api/VitessceConfig.js', () => {
446
451
  spatialSegmentationChannel: {
447
452
  obsType: { A: 'A', B: 'B', C: 'C' },
448
453
  spatialTargetC: { A: 'C', B: 'D', C: 'E' },
449
- spatialChannelColor: { A: 'C', B: 'D', C: 'E' },
454
+ spatialChannelColor: { A: 'A', B: 'A', C: 'A' },
450
455
  },
451
456
  },
452
457
  x: 0,
@@ -465,7 +470,7 @@ describe('src/api/VitessceConfig.js', () => {
465
470
  });
466
471
  const dataset = config.addDataset('My dataset');
467
472
 
468
- const scopes = config.addComplexCoordination({
473
+ const scopes = config.addCoordinationByObject({
469
474
  spatialImageLayer: CL([
470
475
  {
471
476
  image: 'S-1905-017737_bf',
@@ -510,7 +515,7 @@ describe('src/api/VitessceConfig.js', () => {
510
515
  });
511
516
 
512
517
  const metaCoordinationScope = config.addMetaCoordination();
513
- metaCoordinationScope.useComplexCoordination(scopes);
518
+ metaCoordinationScope.useCoordinationByObject(scopes);
514
519
 
515
520
  const spatialView = config.addView(dataset, 'spatial');
516
521
  const lcView = config.addView(dataset, 'layerController');
@@ -601,6 +606,144 @@ describe('src/api/VitessceConfig.js', () => {
601
606
  });
602
607
  });
603
608
 
609
+ it('can use _meta_ complex coordination via the linkViewsByObject convenience function', () => {
610
+ const config = new VitessceConfig({
611
+ schemaVersion: '1.0.16',
612
+ name: 'My config',
613
+ });
614
+ const dataset = config.addDataset('My dataset');
615
+
616
+ const spatialView = config.addView(dataset, 'spatial');
617
+ const lcView = config.addView(dataset, 'layerController');
618
+
619
+ config.linkViewsByObject([spatialView, lcView], {
620
+ spatialImageLayer: CL([
621
+ {
622
+ image: 'S-1905-017737_bf',
623
+ spatialLayerVisible: true,
624
+ spatialLayerOpacity: 1,
625
+ spatialImageChannel: CL([
626
+ {
627
+ spatialTargetC: 0,
628
+ spatialChannelColor: [255, 0, 0],
629
+ },
630
+ {
631
+ spatialTargetC: 1,
632
+ spatialChannelColor: [0, 255, 0],
633
+ },
634
+ ]),
635
+ },
636
+ ]),
637
+ spatialSegmentationLayer: CL([
638
+ {
639
+ image: 'S-1905-017737',
640
+ spatialLayerVisible: true,
641
+ spatialLayerOpacity: 1,
642
+ spatialSegmentationChannel: CL([
643
+ {
644
+ obsType: 'Cortical Interstitia',
645
+ spatialTargetC: 0,
646
+ spatialChannelColor: [255, 0, 0],
647
+ },
648
+ {
649
+ obsType: 'Non-Globally Sclerotic Glomeruli',
650
+ spatialTargetC: 1,
651
+ spatialChannelColor: [255, 0, 0],
652
+ },
653
+ {
654
+ obsType: 'Globally Sclerotic Glomeruli',
655
+ spatialTargetC: 2,
656
+ spatialChannelColor: [255, 0, 0],
657
+ },
658
+ ]),
659
+ },
660
+ ]),
661
+ });
662
+
663
+ const configJSON = config.toJSON();
664
+ expect(configJSON).toEqual({
665
+ version: '1.0.16',
666
+ name: 'My config',
667
+ datasets: [{ uid: 'A', name: 'My dataset', files: [] }],
668
+ coordinationSpace: {
669
+ dataset: { A: 'A' },
670
+ spatialImageLayer: { A: '__dummy__' },
671
+ image: { A: 'S-1905-017737_bf', B: 'S-1905-017737' },
672
+ spatialLayerVisible: { A: true, B: true },
673
+ spatialLayerOpacity: { A: 1, B: 1 },
674
+ spatialImageChannel: { A: '__dummy__', B: '__dummy__' },
675
+ spatialTargetC: {
676
+ A: 0, B: 1, C: 0, D: 1, E: 2,
677
+ },
678
+ spatialChannelColor: {
679
+ A: [255, 0, 0],
680
+ B: [0, 255, 0],
681
+ C: [255, 0, 0],
682
+ D: [255, 0, 0],
683
+ E: [255, 0, 0],
684
+ },
685
+ spatialSegmentationLayer: { A: '__dummy__' },
686
+ spatialSegmentationChannel: { A: '__dummy__', B: '__dummy__', C: '__dummy__' },
687
+ obsType: {
688
+ A: 'Cortical Interstitia',
689
+ B: 'Non-Globally Sclerotic Glomeruli',
690
+ C: 'Globally Sclerotic Glomeruli',
691
+ },
692
+ metaCoordinationScopes: {
693
+ A: {
694
+ spatialImageLayer: ['A'],
695
+ spatialSegmentationLayer: ['A'],
696
+ },
697
+ },
698
+ metaCoordinationScopesBy: {
699
+ A: {
700
+ spatialImageLayer: {
701
+ image: { A: 'A' },
702
+ spatialLayerVisible: { A: 'A' },
703
+ spatialLayerOpacity: { A: 'A' },
704
+ spatialImageChannel: { A: ['A', 'B'] },
705
+ },
706
+ spatialImageChannel: {
707
+ spatialTargetC: { A: 'A', B: 'B' },
708
+ spatialChannelColor: { A: 'A', B: 'B' },
709
+ },
710
+ spatialSegmentationLayer: {
711
+ image: { A: 'B' },
712
+ spatialLayerVisible: { A: 'B' },
713
+ spatialLayerOpacity: { A: 'B' },
714
+ spatialSegmentationChannel: { A: ['A', 'B', 'C'] },
715
+ },
716
+ spatialSegmentationChannel: {
717
+ obsType: { A: 'A', B: 'B', C: 'C' },
718
+ spatialTargetC: { A: 'C', B: 'D', C: 'E' },
719
+ spatialChannelColor: { A: 'C', B: 'D', C: 'E' },
720
+ },
721
+ },
722
+ },
723
+ },
724
+ layout: [{
725
+ component: 'spatial',
726
+ coordinationScopes: {
727
+ dataset: 'A',
728
+ metaCoordinationScopes: ['A'],
729
+ metaCoordinationScopesBy: ['A'],
730
+ },
731
+ // eslint-disable-next-line object-property-newline
732
+ x: 0, y: 0, w: 1, h: 1,
733
+ }, {
734
+ component: 'layerController',
735
+ coordinationScopes: {
736
+ dataset: 'A',
737
+ metaCoordinationScopes: ['A'],
738
+ metaCoordinationScopesBy: ['A'],
739
+ },
740
+ // eslint-disable-next-line object-property-newline
741
+ x: 0, y: 0, w: 1, h: 1,
742
+ }],
743
+ initStrategy: 'auto',
744
+ });
745
+ });
746
+
604
747
  it('can add a coordination scope using the link views convenience function', () => {
605
748
  const config = new VitessceConfig({
606
749
  schemaVersion: '1.0.4',
package/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { VitessceConfig, vconcat, hconcat } from './VitessceConfig.js';
1
+ export { VitessceConfig, vconcat, hconcat, CL as CoordinationLevel } from './VitessceConfig.js';
2
2
  export { generateConfig, getHintOptions } from './VitessceAutoConfig.js';
3
3
  export { HINTS_CONFIG, HINT_TYPE_TO_FILE_TYPE_MAP } from './constants.js';