Skip to content

Commit 3198236

Browse files
committed
feat: add experiment.import_resources()
1 parent 4328ffe commit 3198236

File tree

3 files changed

+92
-1
lines changed

3 files changed

+92
-1
lines changed

cellengine/resources/experiment.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
from pandas.core.frame import DataFrame
1111

12+
try:
13+
from typing import TypedDict, NotRequired
14+
except ImportError:
15+
from typing_extensions import TypedDict, NotRequired
16+
1217
import cellengine as ce
1318
from cellengine.resources.attachment import Attachment
1419
from cellengine.resources.compensation import Compensation, UNCOMPENSATED, Compensations
@@ -31,6 +36,18 @@
3136
)
3237

3338

39+
ImportOpts = TypedDict(
40+
"ImportOpts",
41+
{
42+
"populations": NotRequired[bool],
43+
"illustrations": NotRequired[Union[bool, List[str]]],
44+
"compensations": NotRequired[Union[bool, List[str]]],
45+
"savedStatisticExports": NotRequired[Union[bool, List[str]]],
46+
"annotations": NotRequired[Union[bool, List[str]]],
47+
},
48+
)
49+
50+
3451
class Experiment:
3552
"""The main container for an analysis. Don't construct directly; use
3653
[`Experiment.create`][cellengine.Experiment.create] or
@@ -374,6 +391,46 @@ def save_revision(self, description: str) -> None:
374391
self._properties["revisions"] = r.get("revisions")
375392
self._properties["deepUpdated"] = r.get("deepUpdated")
376393

394+
def import_resources(
395+
self,
396+
srcExperimentId: str,
397+
what: ImportOpts,
398+
channelMap: Optional[Dict[str, str]] = {},
399+
dstPopulationId: Optional[str] = None,
400+
) -> None:
401+
"""
402+
Imports resources from another experiment.
403+
404+
Args:
405+
srcExperimentId (str): The ID of the source experiment.
406+
what (ImportOpts): A dictionary with the following optional keys:
407+
- populations: Whether to import populations.
408+
- illustrations: Whether to import illustrations (True = all, False = none),
409+
or a list of specific illustration IDs to import.
410+
- compensations: Whether to import compensations (True = all, False = none),
411+
or a list of specific compensation IDs to import.
412+
- savedStatisticExports: Whether to import saved statistic exports
413+
(True = all, False = none), or a list of specific export IDs to import.
414+
- annotations: Whether to import annotations (True = all, False = none),
415+
or a list of specific annotation names to import.
416+
channelMap (Dict[str, str]): A dictionary Object mapping channel
417+
names from source experiment to destination experiment for
418+
imported gates. Gates using channels not present in the map or
419+
with channels set to the value "" will not be imported.
420+
Populations are only imported if all of their required gates are
421+
imported (i.e. the entire set of parents must be imported). This
422+
does not affect compensation import.
423+
dstPopulationId (str): The ID of the destination parent population.
424+
If not provided, the root population is used.
425+
426+
Use `experiment.gates` and similar to access the imported resources.
427+
*Note: If it would be useful for this method to return the imported
428+
resources, open a GitHub issue letting us know.*
429+
"""
430+
ce.APIClient().import_experiment_resources(
431+
self._id, srcExperimentId, what, dstPopulationId, channelMap
432+
)
433+
377434
# Attachments
378435

379436
@property

cellengine/utils/api_client/APIClient.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
from ...resources.attachment import Attachment
2626
from ...resources.compensation import Compensation, Compensations, UNCOMPENSATED
27-
from ...resources.experiment import Experiment
27+
from ...resources.experiment import Experiment, ImportOpts
2828
from ...resources.fcs_file import FcsFile
2929
from ...resources.folder import Folder
3030
from ...resources.gate import (
@@ -358,6 +358,24 @@ def save_experiment_revision(self, _id, description: str) -> Dict:
358358
json={"description": description},
359359
)
360360

361+
def import_experiment_resources(
362+
self,
363+
experiment_id: str,
364+
srcExperimentId: str,
365+
what: ImportOpts,
366+
dstPopulationId: Optional[str],
367+
channelMap: Optional[Dict[str, str]],
368+
) -> None:
369+
r = self._post(
370+
f"{self.base_url}/api/v1/experiments/{experiment_id}/importResources",
371+
json={
372+
"srcExperiment": srcExperimentId,
373+
"dstPopulationId": dstPopulationId,
374+
"channelMap": channelMap,
375+
"import": what,
376+
},
377+
)
378+
361379
# ------------------------------- FCS Files --------------------------------
362380

363381
def get_fcs_files(self, experiment_id, as_dict=False) -> List[FcsFile]:

tests/integration/test_experiment.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ def test_save_revision(blank_experiment):
119119
assert blank_experiment.deep_updated > preDU
120120

121121

122+
def test_experiment_import_resources(full_experiment):
123+
dest = Experiment.create("dest")
124+
dest.upload_fcs_file("tests/data/Specimen_001_A1_A01_MeOHperm(DL350neg).fcs")
125+
dest.import_resources(
126+
full_experiment["experiment"]._id,
127+
{"populations": True, "compensations": True},
128+
{
129+
"FSC-A": "FSC-A",
130+
"SSC-A": "SSC-A",
131+
},
132+
)
133+
assert len(dest.gates) == 1
134+
assert len(dest.populations) == 1
135+
assert len(dest.compensations) == 1
136+
137+
122138
def test_experiment_upload_fcs_file(blank_experiment: Experiment):
123139
file = blank_experiment.upload_fcs_file(
124140
"tests/data/Specimen_001_A1_A01_MeOHperm(DL350neg).fcs"

0 commit comments

Comments
 (0)