StructureCloud 0.0.1__py3-none-any.whl
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.
- StructureCloud/Datasets/__init__.py +1 -0
- StructureCloud/Datasets/preprocess.py +373 -0
- StructureCloud/Datasets/retrieval.py +53 -0
- structurecloud-0.0.1.dist-info/METADATA +101 -0
- structurecloud-0.0.1.dist-info/RECORD +8 -0
- structurecloud-0.0.1.dist-info/WHEEL +5 -0
- structurecloud-0.0.1.dist-info/licenses/LICENSE +21 -0
- structurecloud-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .retrieval import StructureCloudDataset
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import csv
|
|
4
|
+
import json
|
|
5
|
+
import pickle
|
|
6
|
+
import multiprocessing as mp
|
|
7
|
+
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import torch
|
|
12
|
+
from rdkit import Chem
|
|
13
|
+
from tqdm import tqdm
|
|
14
|
+
|
|
15
|
+
import gzip
|
|
16
|
+
import shutil
|
|
17
|
+
|
|
18
|
+
'''
|
|
19
|
+
USEFUL FUNCTIONS FOR DATASET STATS PLOT AND SAVING
|
|
20
|
+
|
|
21
|
+
Instructions:
|
|
22
|
+
1. create some iterable that load your source data in the following format:
|
|
23
|
+
|
|
24
|
+
node_pos, node_class, unitcell, labels = dataset[idx]
|
|
25
|
+
note: labels is a dictionary of one or two dictionaries containing labels
|
|
26
|
+
labels[object] = object wise labels
|
|
27
|
+
labels[node] = node wise labels
|
|
28
|
+
|
|
29
|
+
2. pass this iterable to the dataset_histogram function and save the plot
|
|
30
|
+
|
|
31
|
+
fig, stat_dict = dataset_histogram(dataset, max_sample_size=-1, full_data_range=True, random_order=True)
|
|
32
|
+
fig.savefig('dataset_histogram.png')
|
|
33
|
+
|
|
34
|
+
if your dataset is too large, you can set max_sample_size to a smaller number
|
|
35
|
+
|
|
36
|
+
3. save your dataset into a set of chunks in either a json or pickle file format
|
|
37
|
+
|
|
38
|
+
save_to_chunks( dataset,
|
|
39
|
+
output_directory, # the directory to save data chunks to, will be made if it does not exist
|
|
40
|
+
chunk_size=-1, # this means the entire dataset will be saved in one file
|
|
41
|
+
file_type='pkl', # 'pkl' or 'json'
|
|
42
|
+
jobs=1, # number of parallel jobs to use for saving. Default is 1 (no parallelism).
|
|
43
|
+
progress_bar=True # show a progress tqdm bar for saving in a notebook
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
'''
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def cannonicalize_smiles(smiles):
|
|
51
|
+
"""
|
|
52
|
+
Convert a SMILES string to its canonical form.
|
|
53
|
+
"""
|
|
54
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
55
|
+
if mol is None:
|
|
56
|
+
# raise ValueError(f'Invalid SMILES string: "{smiles}" ')
|
|
57
|
+
print(f'Invalid SMILES string!! : "{smiles}" ')
|
|
58
|
+
return None
|
|
59
|
+
return Chem.MolToSmiles(mol, canonical=True)
|
|
60
|
+
|
|
61
|
+
def get_bounding_box(points):
|
|
62
|
+
"""
|
|
63
|
+
Calculate the bounding box of a set of points.
|
|
64
|
+
"""
|
|
65
|
+
if isinstance(points, torch.Tensor):
|
|
66
|
+
points = points.detach().cpu().numpy()
|
|
67
|
+
|
|
68
|
+
min_x = np.min(points[:, 0])
|
|
69
|
+
max_x = np.max(points[:, 0])
|
|
70
|
+
min_y = np.min(points[:, 1])
|
|
71
|
+
max_y = np.max(points[:, 1])
|
|
72
|
+
min_z = np.min(points[:, 2])
|
|
73
|
+
max_z = np.max(points[:, 2])
|
|
74
|
+
|
|
75
|
+
length_x = max_x - min_x
|
|
76
|
+
length_y = max_y - min_y
|
|
77
|
+
length_z = max_z - min_z
|
|
78
|
+
|
|
79
|
+
#convert into a unit cell, a unit cell is a 3x3 matrix
|
|
80
|
+
unit_cell = np.array([[length_x, 0, 0],
|
|
81
|
+
[0, length_y, 0],
|
|
82
|
+
[0, 0, length_z]])
|
|
83
|
+
|
|
84
|
+
return torch.tensor(unit_cell)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
from scipy.spatial.distance import pdist
|
|
88
|
+
def longest_unit_cell_diagonal(cell: np.ndarray) -> float:
|
|
89
|
+
indices = np.array([[i, j, k] for i in [0, 1] for j in [0, 1] for k in [0, 1]])
|
|
90
|
+
corners = indices @ cell.T
|
|
91
|
+
return np.max(pdist(corners))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def dataset_histogram(dataset, max_sample_size : int =100000, full_data_range: bool =True, random_order : bool = True, seed=12345) -> plt.Figure:
|
|
96
|
+
'''
|
|
97
|
+
input needs to be cannonical dataset with output format pos, node_class, unit_cell, labels
|
|
98
|
+
dataset (iterable) : object providing samples
|
|
99
|
+
max_sample_size (int) : maximum number of samples to use for the histograms.
|
|
100
|
+
sampling the entire dataset may be slow, or may not fit in memory.
|
|
101
|
+
if max_sample_size < 1, this will sample the entire dataset.
|
|
102
|
+
full_data_range (bool) : if True, this will iterate over the entire dataset to compute the range (min,max) of
|
|
103
|
+
number of points (N), length of the unit cell (min, max), and range of elements (n_total)
|
|
104
|
+
random_order (bool) : if True, this will shuffle the dataset before sampling.
|
|
105
|
+
seed (int) : random seed for reproducibility
|
|
106
|
+
|
|
107
|
+
Example usage:
|
|
108
|
+
fig = dataset_histogram(dataset, max_sample_size=-1, full_data_range=True, random_order=True)
|
|
109
|
+
fig.savefig('dataset_histogram.png')
|
|
110
|
+
'''
|
|
111
|
+
|
|
112
|
+
if max_sample_size < 1:
|
|
113
|
+
max_sample_size = len(dataset)
|
|
114
|
+
|
|
115
|
+
max_N = -1
|
|
116
|
+
min_N = np.inf
|
|
117
|
+
|
|
118
|
+
max_ldiag = -1
|
|
119
|
+
min_ldiag = np.inf
|
|
120
|
+
max_lxyz = -1
|
|
121
|
+
min_lxyz = np.inf
|
|
122
|
+
unique_node_id_count = -1 # number of uniques elements in dataset
|
|
123
|
+
|
|
124
|
+
N_sample = []
|
|
125
|
+
lxyz_sample = []
|
|
126
|
+
ldiag_sample = []
|
|
127
|
+
id_samples = []
|
|
128
|
+
id_count = {} # store a count of unique elements/node classes in the dataset
|
|
129
|
+
n_count = {}
|
|
130
|
+
|
|
131
|
+
order = np.arange(len(dataset))
|
|
132
|
+
if random_order:
|
|
133
|
+
rand_gen = np.random.default_rng(seed)
|
|
134
|
+
rand_gen.shuffle(order)
|
|
135
|
+
|
|
136
|
+
for i, idx in enumerate(tqdm(order)):
|
|
137
|
+
#assume outputs are in tensor or numpy format
|
|
138
|
+
pos, node_id, unit_cell, labels = dataset[idx]
|
|
139
|
+
|
|
140
|
+
N = pos.shape[0]
|
|
141
|
+
if isinstance(N, torch.Tensor):
|
|
142
|
+
N = N.item()
|
|
143
|
+
|
|
144
|
+
node_id = node_id.flatten()
|
|
145
|
+
if isinstance(node_id, torch.Tensor):
|
|
146
|
+
node_id = node_id.tolist()
|
|
147
|
+
|
|
148
|
+
if N > max_N:
|
|
149
|
+
max_N = N
|
|
150
|
+
if N < min_N:
|
|
151
|
+
min_N = N
|
|
152
|
+
|
|
153
|
+
#check if N is in n_count
|
|
154
|
+
if N in n_count:
|
|
155
|
+
n_count[N] += 1
|
|
156
|
+
else:
|
|
157
|
+
n_count[N] = 1
|
|
158
|
+
|
|
159
|
+
for id in node_id:
|
|
160
|
+
assert isinstance(id, int), f"node_id must be an integer, got {type(id)}"
|
|
161
|
+
if id in id_count:
|
|
162
|
+
id_count[id] += 1
|
|
163
|
+
else:
|
|
164
|
+
id_count[id] = 1
|
|
165
|
+
unique_node_id_count += 1
|
|
166
|
+
|
|
167
|
+
#orthogonalize the unit cell to get the lengths
|
|
168
|
+
lxyz = np.linalg.norm(unit_cell, axis=1).reshape(-1) #NOTE: this assumes lattice vectores are stacked on dim 0
|
|
169
|
+
lxyz_max_i = np.max(lxyz)
|
|
170
|
+
lxyz_min_i = np.min(lxyz)
|
|
171
|
+
|
|
172
|
+
if lxyz_max_i > max_lxyz:
|
|
173
|
+
max_lxyz = lxyz_max_i
|
|
174
|
+
if lxyz_min_i < min_lxyz:
|
|
175
|
+
min_lxyz = lxyz_min_i
|
|
176
|
+
|
|
177
|
+
ldiag_i = longest_unit_cell_diagonal(unit_cell.numpy())
|
|
178
|
+
if ldiag_i > max_ldiag:
|
|
179
|
+
max_ldiag = ldiag_i
|
|
180
|
+
if ldiag_i < min_ldiag:
|
|
181
|
+
min_ldiag = ldiag_i
|
|
182
|
+
|
|
183
|
+
if i <= max_sample_size-1:
|
|
184
|
+
N_sample.append(N)
|
|
185
|
+
id_samples.extend(node_id)
|
|
186
|
+
lxyz_sample.extend(lxyz.tolist())
|
|
187
|
+
ldiag_sample.append(ldiag_i)
|
|
188
|
+
elif not full_data_range:
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
range_dict = {}
|
|
192
|
+
range_dict['N'] = (min_N, max_N)
|
|
193
|
+
range_dict['Lxyz'] = (min_lxyz, max_lxyz)
|
|
194
|
+
range_dict['Ldiag'] = (min_ldiag, max_ldiag)
|
|
195
|
+
range_dict['unique_node_ids'] = id_count.keys()
|
|
196
|
+
range_dict['unique_node_ids_count'] = len(id_count.keys())
|
|
197
|
+
|
|
198
|
+
N_bins = np.arange(min_N, max_N+1)
|
|
199
|
+
L_bins = np.linspace(min_lxyz, max_ldiag, 100)
|
|
200
|
+
|
|
201
|
+
#plot histograms
|
|
202
|
+
fig_scale = 2
|
|
203
|
+
fig, ax = plt.subplots(3, 1, figsize=(5*fig_scale, 5*fig_scale))
|
|
204
|
+
ax[0].hist(N_sample, bins=N_bins, density=True, alpha=1)
|
|
205
|
+
ax[0].set_xlabel('Number of atoms (N)')
|
|
206
|
+
ax[0].set_ylabel('Fraction')
|
|
207
|
+
ax[0].set_title('Number of nodes in dataset: min={}, max={}'.format(min_N, max_N))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
ax[1].hist(lxyz_sample, bins=L_bins, density=True, alpha=0.6, label='lxyz')
|
|
211
|
+
ax[1].hist(ldiag_sample, bins=L_bins, density=True, alpha=0.6, label='ldiag')
|
|
212
|
+
ax[1].set_xlabel('Length of unit cell side (lxyz) and max diagonal (ldiag)')
|
|
213
|
+
ax[1].set_ylabel('Fraction')
|
|
214
|
+
ax[1].legend()
|
|
215
|
+
ax[1].set_title(f'Unitcell lengths in dataset: min={min_lxyz:0.3f}, max={max_ldiag:0.2f}')
|
|
216
|
+
|
|
217
|
+
#use id_count dictionary to plot a bar graph of the number of unique elements
|
|
218
|
+
id_max_count = max(id_count.values())
|
|
219
|
+
id_relative_count = [v/id_max_count for v in id_count.values()]
|
|
220
|
+
|
|
221
|
+
#convert id_count.keys() to a list
|
|
222
|
+
string_ids = [str(id) for id in np.sort(list(id_count.keys()))] #.to_list().sort
|
|
223
|
+
ax[2].bar(string_ids, id_relative_count)
|
|
224
|
+
ax[2].set_xlabel('Unique node ids (elements)')
|
|
225
|
+
ax[2].set_ylabel('Relative Count')
|
|
226
|
+
|
|
227
|
+
fig.suptitle('Histogram sample size: {}'.format(len(N_sample)))
|
|
228
|
+
fig.tight_layout()
|
|
229
|
+
fig.subplots_adjust(top=0.9)
|
|
230
|
+
|
|
231
|
+
return fig, range_dict
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
#### functions for saving to files ###
|
|
236
|
+
|
|
237
|
+
def format_values(value):
|
|
238
|
+
"""
|
|
239
|
+
Convert a value to a format suitable for JSON serialization.
|
|
240
|
+
"""
|
|
241
|
+
if isinstance(value, torch.Tensor):
|
|
242
|
+
return value.detach().cpu().tolist()
|
|
243
|
+
elif isinstance(value, np.ndarray):
|
|
244
|
+
return value.tolist()
|
|
245
|
+
elif isinstance(value, list):
|
|
246
|
+
return [format_values(v) for v in value]
|
|
247
|
+
elif isinstance(value, dict):
|
|
248
|
+
return {k: format_values(v) for k, v in value.items()}
|
|
249
|
+
else:
|
|
250
|
+
return value
|
|
251
|
+
|
|
252
|
+
def write_chunk(chunk, chunk_name : str, output_dir : str, file_type : str, compress : bool):
|
|
253
|
+
'''
|
|
254
|
+
chunk : list of dictionaries, each dictionary is a sample
|
|
255
|
+
'''
|
|
256
|
+
file_ext = {'json': 'jsonl', # default to jsonl
|
|
257
|
+
'jsonl': 'jsonl', # default to jsonl
|
|
258
|
+
'pkl': 'pkl',
|
|
259
|
+
'pickle' : 'pkl'}[file_type]
|
|
260
|
+
|
|
261
|
+
chunk_path = os.path.join(output_dir, f'{chunk_name}.{file_ext}')
|
|
262
|
+
open_cmd = gzip.open if compress else open
|
|
263
|
+
if compress:
|
|
264
|
+
# add .gz to the filename
|
|
265
|
+
chunk_path += '.gz'
|
|
266
|
+
|
|
267
|
+
if file_ext == 'pkl':
|
|
268
|
+
with open_cmd(chunk_path, 'wb') as f:
|
|
269
|
+
pickle.dump(chunk, f)
|
|
270
|
+
elif file_ext == 'jsonl':
|
|
271
|
+
with open_cmd(chunk_path, 'wt', encoding='utf-8') as f:
|
|
272
|
+
json_lines = [json.dumps(item) for item in chunk]
|
|
273
|
+
f.write('\n'.join(json_lines) + '\n')
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError(f"Unsupported file type: {file_type}. Supported types are: {list(file_ext.keys())}")
|
|
276
|
+
|
|
277
|
+
def process_dataset_chunk(dataset, data_indices, chunk_name, output_dir, file_type, compress, progress_bar=False):
|
|
278
|
+
if progress_bar:
|
|
279
|
+
data_indices = tqdm(data_indices, desc=f"Formating {chunk_name}")
|
|
280
|
+
|
|
281
|
+
data_list = []
|
|
282
|
+
for idx in data_indices:
|
|
283
|
+
position, node_class, unitcell, labels = dataset[idx]
|
|
284
|
+
|
|
285
|
+
# Format each sample
|
|
286
|
+
sample = {
|
|
287
|
+
'position': format_values(position),
|
|
288
|
+
'node_class': format_values(node_class),
|
|
289
|
+
'unitcell': format_values(unitcell),
|
|
290
|
+
'labels': format_values(labels),
|
|
291
|
+
}
|
|
292
|
+
data_list.append(sample)
|
|
293
|
+
|
|
294
|
+
if progress_bar:
|
|
295
|
+
print(f"Writing chunk {chunk_name} with {len(data_list)} samples")
|
|
296
|
+
write_chunk(data_list, chunk_name, output_dir, file_type, compress)
|
|
297
|
+
return chunk_name
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def save_to_chunks(dataset, output_dir, chunk_size=-1, file_type='json', jobs=1,
|
|
301
|
+
prefix='chunk', progress_bar=True, compress=True):
|
|
302
|
+
"""
|
|
303
|
+
Save the entire dataset to a single JSON file or series of JSON files for a given chunksize.
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
dataset (iterable): Object providing samples in the standard format.
|
|
308
|
+
output_path (str): Path to save the JSON file.
|
|
309
|
+
chunk_size (int): Number of samples to save in each chunk.
|
|
310
|
+
If -1, the entire dataset will be saved in one file.
|
|
311
|
+
note: chunking will try and maintain fiel size regularity
|
|
312
|
+
actual chunking may not be the same as chunk_size
|
|
313
|
+
file_type (str): Type of file to save. 'json' or 'pkl' for pickle.
|
|
314
|
+
jobs (int): Number of parallel jobs to use for saving. Default is 1 (no parallelism).
|
|
315
|
+
prefix (str): Prefix for the chunk files. Default is 'chunk_i'.
|
|
316
|
+
compress (bool): If True, compress the JSON file using gzip. Default is True.
|
|
317
|
+
|
|
318
|
+
Dataset sample output must be:
|
|
319
|
+
position, node_class, unitcell, labels = dataset[idx]
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
# Ensure output directory exists
|
|
323
|
+
if not os.path.exists(output_dir):
|
|
324
|
+
print(f"Output directory {output_dir} does not exist. Creating directory.")
|
|
325
|
+
os.makedirs(output_dir, exist_ok=False)
|
|
326
|
+
|
|
327
|
+
if chunk_size is None or chunk_size < 0:
|
|
328
|
+
#save the entire dataset in one file
|
|
329
|
+
chunk_size = len(dataset)
|
|
330
|
+
|
|
331
|
+
#determine the number of chunks
|
|
332
|
+
num_chunks = len(dataset) // chunk_size + (1 if len(dataset) % chunk_size > 0 else 0)
|
|
333
|
+
print(f"Saving dataset in {num_chunks} chunks of size {chunk_size} with {jobs} jobs...")
|
|
334
|
+
|
|
335
|
+
jobs = max(min(jobs, num_chunks), 1) # limit the number of jobs to the number of chunks
|
|
336
|
+
|
|
337
|
+
all_data_indices = np.arange(len(dataset))
|
|
338
|
+
chunk_indices = np.array_split(all_data_indices, num_chunks)
|
|
339
|
+
print(f"Chunk sizes: {[len(c) for c in chunk_indices]}")
|
|
340
|
+
job_args = []
|
|
341
|
+
for i in range(num_chunks):
|
|
342
|
+
chunk_name = f'{prefix}_{i+1}'
|
|
343
|
+
chunk_indices_i = chunk_indices[i]
|
|
344
|
+
kwargs = {'dataset': dataset,
|
|
345
|
+
'data_indices': chunk_indices_i,
|
|
346
|
+
'chunk_name': chunk_name,
|
|
347
|
+
'output_dir': output_dir,
|
|
348
|
+
'file_type': file_type,
|
|
349
|
+
'compress': compress,}
|
|
350
|
+
job_args.append(kwargs)
|
|
351
|
+
|
|
352
|
+
if jobs <= 1:
|
|
353
|
+
for i, job_args in enumerate(job_args):
|
|
354
|
+
process_dataset_chunk(**job_args, progress_bar=progress_bar)
|
|
355
|
+
else:
|
|
356
|
+
# Use multiprocessing to save chunks in parallel
|
|
357
|
+
def callback_fn(out):
|
|
358
|
+
print(f"{out} saved successfully")
|
|
359
|
+
def err_callback_fn(out):
|
|
360
|
+
print(f"Error in process: {out}")
|
|
361
|
+
|
|
362
|
+
with mp.Pool(processes=jobs) as pool:
|
|
363
|
+
job_outs = []
|
|
364
|
+
for i, kwargs in enumerate(job_args):
|
|
365
|
+
res = pool.apply_async(process_dataset_chunk, kwds=kwargs, callback=callback_fn, error_callback=err_callback_fn)
|
|
366
|
+
job_outs.append(res)
|
|
367
|
+
|
|
368
|
+
pool.close()
|
|
369
|
+
pool.join()
|
|
370
|
+
print("All jobs complete!")
|
|
371
|
+
|
|
372
|
+
print(f"Data saved to {output_dir}")
|
|
373
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
from datasets import load_dataset
|
|
4
|
+
from datasets.dataset_dict import DatasetDict
|
|
5
|
+
|
|
6
|
+
class StructureCloudDataset():
|
|
7
|
+
'''
|
|
8
|
+
A very simple class for loading StructureCloud datasets
|
|
9
|
+
'''
|
|
10
|
+
def __init__(self, dataset_name, num_fmt : callable = np.array, label_fmt : callable = None, **huggingface_kwargs):
|
|
11
|
+
'''
|
|
12
|
+
Args:
|
|
13
|
+
dataset_name (str): name of the dataset to load
|
|
14
|
+
num_fmt (type): the numerical format to use for the data.
|
|
15
|
+
default is numpy array, but can be changed to torch tensor or other formats
|
|
16
|
+
label_fmt (type): a function that takes in the labels and returns a desired format of the labels.
|
|
17
|
+
default is None, which means labels are returned as loaded
|
|
18
|
+
huggingface_kwargs (dict): additional arguments to pass to load_dataset
|
|
19
|
+
|
|
20
|
+
Default numerical format is numpy array
|
|
21
|
+
'''
|
|
22
|
+
|
|
23
|
+
if label_fmt is None:
|
|
24
|
+
label_fmt = lambda x: x # no formatting
|
|
25
|
+
|
|
26
|
+
self.label_fmt = label_fmt
|
|
27
|
+
self.num_fmt = num_fmt
|
|
28
|
+
self.dataset_name = dataset_name
|
|
29
|
+
|
|
30
|
+
ds = load_dataset(
|
|
31
|
+
f'StructureCloud/{dataset_name}',
|
|
32
|
+
**huggingface_kwargs
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if isinstance(ds, DatasetDict):
|
|
36
|
+
splits = list(ds.keys())
|
|
37
|
+
if len(splits) > 1:
|
|
38
|
+
print(f'Multiple splits found, but none specified: {splits} \nDefaulting to {splits[0]}')
|
|
39
|
+
ds = ds[splits[0]]
|
|
40
|
+
|
|
41
|
+
self.data = ds
|
|
42
|
+
|
|
43
|
+
def __len__(self):
|
|
44
|
+
return len(self.data)
|
|
45
|
+
|
|
46
|
+
def __getitem__(self, idx):
|
|
47
|
+
#get the data at select index and reformat it
|
|
48
|
+
data = self.data[idx]
|
|
49
|
+
pos = self.num_fmt(data['position'])
|
|
50
|
+
node_class = self.num_fmt(data['node_class'])
|
|
51
|
+
unit_cell = self.num_fmt(data['unitcell'])
|
|
52
|
+
labels = self.label_fmt(data['labels'])
|
|
53
|
+
return pos, node_class, unit_cell, labels
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: StructureCloud
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Standard formating and easy access to 3D structural datasets for machine learning. currently under development...
|
|
5
|
+
Author-email: Ty Perez <ty.jperez@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/TyJPerez/StructureCloud
|
|
8
|
+
Project-URL: Issues, https://github.com/TyJPerez/StructureCloud/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# StructureCloud
|
|
17
|
+
A collection of 3D Point cloud datasets commonly used for training generative modeling and molecular discovery.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
install hugging face dataset library
|
|
22
|
+
```bash
|
|
23
|
+
pip install datasets
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
install StructureCloud (TODO: not yet on PyPi)
|
|
27
|
+
```bash
|
|
28
|
+
$ pip install StructureCloud
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## StructureCloud Datasets
|
|
33
|
+
StructureCloud is aimed at simplifying pointcloud dataset retrieval and manipulation with a simple unifying output format. The datasets in StructureCloud are chosen speifically for training models that adress problems related to 3D structures.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
```python
|
|
38
|
+
import numpy as np
|
|
39
|
+
import torch
|
|
40
|
+
from StructureCloud.Datasets import StructureCloudDataset as scd
|
|
41
|
+
|
|
42
|
+
# load numerical values as numpy objects
|
|
43
|
+
dataset_default = scd('dataset_name', split='train')
|
|
44
|
+
|
|
45
|
+
# load numerical values as torch tensors
|
|
46
|
+
dataset_torch = scd('dataset_name', split='train', num_fmt=torch.tensor)
|
|
47
|
+
|
|
48
|
+
#dataset output format is as follows
|
|
49
|
+
positions, features, unitcell, lables = dataset[index]
|
|
50
|
+
|
|
51
|
+
# output shapes:
|
|
52
|
+
# positions [N, 3] - positions in 3d space
|
|
53
|
+
# features [N, 1] or [N, d] - node feature/identities (ie element#, class, etc)
|
|
54
|
+
# unitcell/bounding box [3,3]
|
|
55
|
+
# labels - a dictionary
|
|
56
|
+
# lables['node'] = { dict of nodewise lables : [N,d] }
|
|
57
|
+
# labels['object] = { dict of whole object labels : [d] }
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
#### selecting and formating data labels ###
|
|
61
|
+
## by default, the labels dictonary returns all additional information associated with the dataset.
|
|
62
|
+
## but a specific label can be selected by defining a formating function in 'label_fmt'
|
|
63
|
+
|
|
64
|
+
get_object_target = lambda x : torch.tensor(x['object']['targets'])
|
|
65
|
+
get_object_smiles = lambda x : x['object']['SMILES']
|
|
66
|
+
|
|
67
|
+
qm9_regression = StructureCloudDataset('QM9', label_fmt = get_object_target)
|
|
68
|
+
qm9_smiles = StructureCloudDataset('QM9', label_fmt = get_object_smiles )
|
|
69
|
+
|
|
70
|
+
reg_target = qm9_regression[1000][3]
|
|
71
|
+
smiles = qm9_smiles[1000][3]
|
|
72
|
+
print(smiles)
|
|
73
|
+
print(reg_target)
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Available Datasets
|
|
78
|
+
**Small Organic Molecules**
|
|
79
|
+
- QM9
|
|
80
|
+
- PCQM4Mv2
|
|
81
|
+
- GEOM
|
|
82
|
+
|
|
83
|
+
**Proteins and biomolecules**
|
|
84
|
+
- AlphaFold Homo Sapiens (proteins)
|
|
85
|
+
- (LP)PDBbind2020
|
|
86
|
+
|
|
87
|
+
**Materials**
|
|
88
|
+
- MP-20
|
|
89
|
+
- Perov-5
|
|
90
|
+
- Carbon-24
|
|
91
|
+
- MPTS-52
|
|
92
|
+
- PCOD2
|
|
93
|
+
|
|
94
|
+
**3D objects**
|
|
95
|
+
- coming soon...
|
|
96
|
+
|
|
97
|
+
**Gaussian Splats**
|
|
98
|
+
- coming soon..,
|
|
99
|
+
-
|
|
100
|
+
|
|
101
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
StructureCloud/Datasets/__init__.py,sha256=-GaeVr8Yl-LlEflhB2e3ruYA6rLYjmgsVhbDXIwZgoQ,44
|
|
2
|
+
StructureCloud/Datasets/preprocess.py,sha256=BHFzk8PmlFi90rAu7CJDwwKEXBUQIVlN7fKW_lis-a0,13485
|
|
3
|
+
StructureCloud/Datasets/retrieval.py,sha256=A75FrkIszbg7dMCTn2MgGex2j3tmE9B1CUlDVsd4PnA,1941
|
|
4
|
+
structurecloud-0.0.1.dist-info/licenses/LICENSE,sha256=M0UsffaFmIbimAAw2XPN4kv2hqKKT6Mo-pHznzlRdXo,1068
|
|
5
|
+
structurecloud-0.0.1.dist-info/METADATA,sha256=XQ7XFp3egvs3ahEDlxTpi3s8XBoALUEy_E4wvYOHmJM,2821
|
|
6
|
+
structurecloud-0.0.1.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
|
|
7
|
+
structurecloud-0.0.1.dist-info/top_level.txt,sha256=hFrxbO8UXOS4GgjgANtNaTxCdUSkHlhyXYM3liKWpTA,15
|
|
8
|
+
structurecloud-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tynan Perez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
StructureCloud
|