Skip to content

Commit 5628b66

Browse files
jhnwu3John Wu
andauthored
Add/use processor class directly instead of just string (#548)
* new updates to enable direct calls to Processor classes for ease of readability * fixed test cases to use hadm_id and have realistic hybrid approaches * use demo dataset for cost purposes and because the synthetic one would be too costly here * fixed typos and typing --------- Co-authored-by: John Wu <johnwu3@sunlab-work-01.cs.illinois.edu>
1 parent bd636f1 commit 5628b66

5 files changed

Lines changed: 451 additions & 109 deletions

File tree

pyhealth/datasets/sample_dataset.py

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,48 @@
1-
from typing import Dict, List, Optional
1+
from typing import Dict, List, Optional, Union, Type
2+
import inspect
23

34
from torch.utils.data import Dataset
45
from tqdm import tqdm
56

67
from ..processors import get_processor
8+
from ..processors.base_processor import FeatureProcessor
79

810

911
class SampleDataset(Dataset):
1012
"""Sample dataset class for handling and processing data samples.
1113
1214
Attributes:
1315
samples (List[Dict]): List of data samples.
14-
input_schema (Dict[str, str]): Schema for input data.
15-
output_schema (Dict[str, str]): Schema for output data.
16+
input_schema (Dict[str, Union[str, Type[FeatureProcessor]]]):
17+
Schema for input data.
18+
output_schema (Dict[str, Union[str, Type[FeatureProcessor]]]):
19+
Schema for output data.
1620
dataset_name (Optional[str]): Name of the dataset.
1721
task_name (Optional[str]): Name of the task.
1822
"""
1923

2024
def __init__(
2125
self,
2226
samples: List[Dict],
23-
input_schema: Dict[str, str],
24-
output_schema: Dict[str, str],
27+
input_schema: Dict[str, Union[str, Type[FeatureProcessor]]],
28+
output_schema: Dict[str, Union[str, Type[FeatureProcessor]]],
2529
dataset_name: Optional[str] = None,
2630
task_name: Optional[str] = None,
2731
) -> None:
2832
"""Initializes the SampleDataset with samples and schemas.
2933
3034
Args:
3135
samples (List[Dict]): List of data samples.
32-
input_schema (Dict[str, str]): Schema for input data.
33-
output_schema (Dict[str, str]): Schema for output data.
34-
dataset_name (Optional[str], optional): Name of the dataset. Defaults to None.
35-
task_name (Optional[str], optional): Name of the task. Defaults to None.
36+
input_schema (Dict[str, Union[str, Type[FeatureProcessor]]]):
37+
Schema for input data. Values can be string aliases or
38+
processor classes.
39+
output_schema (Dict[str, Union[str, Type[FeatureProcessor]]]):
40+
Schema for output data. Values can be string aliases or
41+
processor classes.
42+
dataset_name (Optional[str], optional): Name of the dataset.
43+
Defaults to None.
44+
task_name (Optional[str], optional): Name of the task.
45+
Defaults to None.
3646
"""
3747
if dataset_name is None:
3848
dataset_name = ""
@@ -48,43 +58,66 @@ def __init__(
4858
# Create patient_to_index and record_to_index mappings
4959
self.patient_to_index = {}
5060
self.record_to_index = {}
51-
61+
5262
for i, sample in enumerate(samples):
5363
# Create patient_to_index mapping
54-
patient_id = sample.get('patient_id')
64+
patient_id = sample.get("patient_id")
5565
if patient_id is not None:
5666
if patient_id not in self.patient_to_index:
5767
self.patient_to_index[patient_id] = []
5868
self.patient_to_index[patient_id].append(i)
59-
69+
6070
# Create record_to_index mapping (optional)
61-
record_id = sample.get('record_id', sample.get('visit_id'))
71+
record_id = sample.get("record_id", sample.get("visit_id"))
6272
if record_id is not None:
6373
if record_id not in self.record_to_index:
6474
self.record_to_index[record_id] = []
6575
self.record_to_index[record_id].append(i)
66-
76+
6777
self.validate()
6878
self.build()
6979

80+
def _get_processor_instance(self, processor_spec):
81+
"""Get processor instance from either string alias or class reference.
82+
83+
Args:
84+
processor_spec: Either a string alias or a processor class
85+
86+
Returns:
87+
Instance of the processor
88+
"""
89+
if isinstance(processor_spec, str):
90+
# Use existing registry system for string aliases
91+
return get_processor(processor_spec)()
92+
elif inspect.isclass(processor_spec) and issubclass(
93+
processor_spec, FeatureProcessor
94+
):
95+
# Direct class reference
96+
return processor_spec()
97+
else:
98+
raise ValueError(
99+
f"Processor spec must be either a string alias or a "
100+
f"FeatureProcessor class, got {type(processor_spec)}"
101+
)
102+
70103
def validate(self) -> None:
71104
"""Validates that the samples match the input and output schemas."""
72105
input_keys = set(self.input_schema.keys())
73106
output_keys = set(self.output_schema.keys())
74107
for s in self.samples:
75-
assert input_keys.issubset(s.keys()), \
76-
"Input schema does not match samples."
77-
assert output_keys.issubset(s.keys()), \
78-
"Output schema does not match samples."
108+
assert input_keys.issubset(s.keys()), "Input schema does not match samples."
109+
assert output_keys.issubset(
110+
s.keys()
111+
), "Output schema does not match samples."
79112
return
80113

81114
def build(self) -> None:
82115
"""Builds the processors for input and output data based on schemas."""
83116
for k, v in self.input_schema.items():
84-
self.input_processors[k] = get_processor(v)()
117+
self.input_processors[k] = self._get_processor_instance(v)
85118
self.input_processors[k].fit(self.samples, k)
86119
for k, v in self.output_schema.items():
87-
self.output_processors[k] = get_processor(v)()
120+
self.output_processors[k] = self._get_processor_instance(v)
88121
self.output_processors[k].fit(self.samples, k)
89122
for sample in tqdm(self.samples, desc="Processing samples"):
90123
for k, v in sample.items():
@@ -101,9 +134,9 @@ def __getitem__(self, index: int) -> Dict:
101134
index (int): Index of the sample to retrieve.
102135
103136
Returns:
104-
Dict: A dict with patient_id, visit_id/record_id, and other task-specific
105-
attributes as key. Conversion to index/tensor will be done
106-
in the model.
137+
Dict: A dict with patient_id, visit_id/record_id, and other
138+
task-specific attributes as key. Conversion to index/tensor
139+
will be done in the model.
107140
"""
108141
return self.samples[index]
109142

pyhealth/tasks/base_task.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
from abc import ABC, abstractmethod
2-
from typing import Dict, List
2+
from typing import Dict, List, Union, Type
33

44
import polars as pl
55

66

77
class BaseTask(ABC):
88
task_name: str
9-
input_schema: Dict[str, str]
10-
output_schema: Dict[str, str]
9+
input_schema: Dict[str, Union[str, Type]]
10+
output_schema: Dict[str, Union[str, Type]]
1111

1212
def pre_filter(self, df: pl.LazyFrame) -> pl.LazyFrame:
1313
return df
1414

1515
@abstractmethod
1616
def __call__(self, patient) -> List[Dict]:
17-
raise NotImplementedError
17+
raise NotImplementedError

0 commit comments

Comments
 (0)