Skip to content

Commit 8b18bd0

Browse files
committed
[SYSTEMDS-#] Add contrastive learning operator for flexible modality pairing
This patch introduces a new operator to Scuro for building contrastive learning pipelines with greater flexibility in handling input modalities. Previously, contrastive pairs had to be structurally aligned in a preprocessing step before being used in Scuro. This limited the ability to work with independently transformed or dynamically generated modalities. The new operator constructs contrastive pairs via a Cartesian product of modalities and optionally extends them with additional modalities that are already aligned. The resulting combinations are evaluated using a user-defined function to determine whether a pair represents a positive or negative sample. Based on this evaluation, the operator outputs both the assigned label and the corresponding modality pair. This design enables dynamic label generation and supports scenarios where modalities are windowed, reshuffled, or transformed differently. It also allows flexible fusion of modalities after contrastive pairing, improving the expressiveness of contrastive learning workflows. Limitations: The Cartesian product can introduce significant computational overhead for large modality sets, which may require further optimization.
1 parent df29d55 commit 8b18bd0

19 files changed

Lines changed: 107 additions & 69 deletions

‎src/main/python/systemds/scuro/dataloader/audio_loader.py‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,17 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
6262
self.file_sanity_check(file)
6363
if not self.load_data_from_file:
6464
import numpy as np
65-
66-
self.metadata[file] = self.modality_type.create_metadata(
67-
1000, np.array([0])
68-
)
65+
audio = np.array([0])
66+
sr = 1000
6967
else:
7068
audio, sr = librosa.load(file, dtype=self._data_type)
7169

7270
if self.normalize:
7371
audio = librosa.util.normalize(audio)
7472

75-
self.metadata[file] = self.modality_type.create_metadata(sr, audio)
73+
self.metadata.append(self.modality_type.create_metadata(sr, audio))
7674

77-
self.data.append(audio)
75+
self.data.append(audio)
7876

7977
def get_stats(self, source_path: str):
8078
sampling_rate = 0

‎src/main/python/systemds/scuro/dataloader/base_loader.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def __init__(
4444
(otherwise please provide your own Dataloader that knows about the file name convention)
4545
"""
4646
self.data = []
47-
self.metadata = (
48-
{}
49-
) # TODO: check what the index should be for storing the metadata (file_name, counter, ...)
47+
self.metadata = []
5048
self.source_path = source_path
5149
self.indices = indices
5250
self.modality_type = modality_type
@@ -87,7 +85,7 @@ def data_type(self, data_type):
8785
def reset(self):
8886
self._next_chunk = 0
8987
self.data = []
90-
self.metadata = {}
88+
self.metadata = []
9189

9290
def load(self):
9391
"""
@@ -134,6 +132,7 @@ def _load_next_chunk(self):
134132
Loads the next chunk of data
135133
"""
136134
self.data = []
135+
# TODO: Handle metadata correctly
137136
next_chunk_indices = self.indices[
138137
self._next_chunk
139138
* self._chunk_size : (self._next_chunk + 1)

‎src/main/python/systemds/scuro/dataloader/image_loader.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
7171

7272
image = image.astype(np.uint8, copy=False)
7373

74-
self.metadata[file] = self.modality_type.create_metadata(
74+
self.metadata.append(self.modality_type.create_metadata(
7575
width, height, channels
76-
)
76+
))
7777

7878
self.data.append(image)
7979

‎src/main/python/systemds/scuro/dataloader/json_loader.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
6969

7070
text = " ".join(text) if isinstance(text, list) else text
7171
self.data.append(text)
72-
self.metadata[idx] = self.modality_type.create_metadata(len(text), text)
72+
self.metadata.append(self.modality_type.create_metadata(len(text), text) | json_file[idx])
7373

7474
def get_stats(self, source_path: str):
7575
self.file_sanity_check(source_path)

‎src/main/python/systemds/scuro/dataloader/pdf_loader.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
6363

6464
image = image.astype(np.uint8, copy=False)
6565

66-
self.metadata[f"{file}_{i}"] = self.modality_type.create_metadata(
66+
self.metadata.append(self.modality_type.create_metadata(
6767
width, height, channels
68-
)
68+
))
6969

7070
self.data.append(image)

‎src/main/python/systemds/scuro/dataloader/text_loader.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
5656
if self.prefix:
5757
line = re.sub(self.prefix, "", line)
5858
line = line.replace("\n", "")
59-
self.metadata[file] = self.modality_type.create_metadata(
59+
self.metadata.append(self.modality_type.create_metadata(
6060
len(line.split()), line
61-
)
61+
))
6262
self.data.append(line)
6363

6464
def get_stats(self, source_path: str):

‎src/main/python/systemds/scuro/dataloader/timeseries_loader.py‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,16 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
8181
data = self._normalize_signals(data)
8282

8383
if file:
84-
self.metadata[index] = self.modality_type.create_metadata(
84+
self.metadata.append(self.modality_type.create_metadata(
8585
self.signal_names, data, self.sampling_rate
86-
)
86+
))
87+
self.data.append(data)
8788
else:
8889
for i, index in enumerate(self.indices):
89-
self.metadata[str(index)] = self.modality_type.create_metadata(
90+
self.metadata.append(self.modality_type.create_metadata(
9091
self.signal_names, data[i], self.sampling_rate
91-
)
92-
self.data.append(data)
92+
))
93+
self.data.append(data[i])
9394

9495
def _normalize_signals(self, data: np.ndarray) -> np.ndarray:
9596
if data.ndim == 1:

‎src/main/python/systemds/scuro/dataloader/transcript_loader.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
5656
md["timestamp_end"] = seg.end
5757
md["text"] = seg.text
5858

59-
self.metadata[f"{file}_{i}"] = md
59+
self.metadata.append(md)
6060

6161
self.data.append(seg.text)

‎src/main/python/systemds/scuro/dataloader/video_loader.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ def extract(self, file: str, index: Optional[Union[str, List[str]]] = None):
8787
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
8888
num_channels = 3
8989

90-
self.metadata[file] = self.modality_type.create_metadata(
90+
self.metadata.append(self.modality_type.create_metadata(
9191
self.fps, length, width, height, num_channels
92-
)
92+
))
9393

9494
frames = []
9595
idx = 0

‎src/main/python/systemds/scuro/modality/joined.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def execute(self, starting_idx=0):
7777
)
7878

7979
for i in range(start, end):
80-
idx_1 = list(self.left_modality.metadata.values())[i + starting_idx][
80+
idx_1 = self.left_modality.metadata[i + starting_idx][
8181
self.condition.leftField
8282
]
8383
if (
@@ -90,7 +90,7 @@ def execute(self, starting_idx=0):
9090
if self.chunk_left:
9191
i = i + starting_idx
9292

93-
idx_2 = list(self.right_modality.metadata.values())[i][
93+
idx_2 = self.right_modality.metadata[i][
9494
self.condition.rightField
9595
]
9696
self.joined_right.data.append([])
@@ -246,11 +246,11 @@ def _apply_representation_chunked(
246246
self.joined_right, representation
247247
)
248248
new_right.data.extend(right_transformed.data)
249-
new_right.metadata.update(right_transformed.metadata)
249+
new_right.metadata.extend(right_transformed.metadata)
250250

251251
left_transformed = self._apply_representation(left_modality, representation)
252252
new_left.data.extend(left_transformed.data)
253-
new_left.metadata.update(left_transformed.metadata)
253+
new_left.metadata.extend(left_transformed.metadata)
254254

255255
new_left.update_metadata()
256256
new_right.update_metadata()

0 commit comments

Comments
 (0)