Accelerating Leigh syndrome drug discovery through deep learning screening in brain organoids.
The 4 matches
- [1] § Methods › Development of DL-based framework for drug repurposing › Step 3: FNNs for drug target prediction ↔ model_library.py, lines 321–412 · score 0.95 · LeakyReLU, weight decay, hidden layer, decay factor, L1, L2
- [2] § Methods › Development of DL-based framework for drug repurposing › Step 2: Denoising and regularization with variational autoencoder (VAE) ↔ model_library.py, lines 226–290 · score 0.94 · fully connected layers, ReLU, batch normalization, initial hidden, encoder, reparameterization
- [3] § Methods › Development of DL-based framework for drug repurposing › Step 1: Graph-based feature embeddings ↔ model_library.py, lines 56–80 · score 0.79 · PolynomialFeatures, min max, bias, expanded, median, sum
- [4] § Methods › Development of DL-based framework for drug repurposing › Step 4: Ranking drugs by Bayesian enrichment score (BES) ↔ model_library.py, lines 435–455 · score 0.53 · normalized rank, posterior, zero, BES, min, hypergeometric
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Python · 457 lines · 22 KB · no license · 4 matches
- import os, sys, time, glob, fcntl, argparse
- from pathlib import Path
- import numpy as np
- import pandas as pd
- import torch
- import torchvision
- from torch import nn
- import torch.nn.functional as F
- import torch.optim as optim
- from torch.utils.data import Dataset, DataLoader
- from torch_sparse import SparseTensor
- import pytorch_lightning as pl
- from tqdm import tqdm
- from sklearn.metrics import confusion_matrix
- from sklearn.preprocessing import PolynomialFeatures, StandardScaler
- from igraph import *
- from torch_geometric.data import Batch
- from torch_geometric.data import DataLoader as gDataLoader
- from torch_geometric.utils import (
- to_undirected, dropout_adj, add_self_loops, degree
- )
- Dtype = 'float32'
- device = 'cpu'
- def get_adj(row, col, value, N, norm=False, asymm_norm=False, set_diag=True, remove_diag=False):
- print(set_diag)
- adj = SparseTensor(row=row, col=col, value=value, sparse_sizes=(N, N))
- if set_diag:
- print('... setting diagonal entries')
- adj = adj.set_diag()
- elif remove_diag:
- print('... removing diagonal entries')
- adj = adj.remove_diag()
- else:
- print('... keeping diag elements as they are')
- if norm==True:
- if not asymm_norm:
- print('... performing symmetric normalization')
- deg = adj.sum(dim=1).to(torch.float)
- deg_inv_sqrt = deg.pow(-0.5)
- deg_inv_sqrt[deg_inv_sqrt == float('inf')] = 0
- adj = deg_inv_sqrt.view(-1, 1) * adj * deg_inv_sqrt.view(1, -1)
- else:
- print('... performing asymmetric normalization')
- deg = adj.sum(dim=1).to(torch.float)
- deg_inv = deg.pow(-1.0)
- deg_inv[deg_inv == float('inf')] = 0
- adj = deg_inv.view(-1, 1) * adj
- else:
- print('... no normalization')
- #
- adj = adj.to_scipy(layout='csr')
- #
- return adj
- def expandAndNormalizeEmbedding(gD, normalization):
- num_nodes = gD[0]['op_embedding'][0].shape[0]
- Xtrain = torch.zeros(len(gD)*gD[0]['op_embedding'][0].shape[0], (2*len(gD[0]['op_embedding'])+len(gD[0]['properties'])) )
- print('All feature train shapes:')
- print(Xtrain.shape)
- for i in range(len(gD)):
- start = i*num_nodes
- end = (i+1)*num_nodes
- op_dict = gD[i]
- Xtrain[start:end,:] = torch.cat([torch.cat(op_dict['op_embedding'],dim=1), torch.cat(op_dict['properties'],dim=1)], axis=1)
- Xtrain = Xtrain.float()
- # aggregation: sum, mean, max, min, median, std, 25%, 75%, difference between sucssessive columns
- Xtrain = torch.cat((Xtrain, torch.sum(Xtrain,1).view(-1,1), torch.mean(Xtrain,1).view(-1,1), torch.max(Xtrain,1).values.view(-1,1), torch.min(Xtrain,1).values.view(-1,1), torch.median(Xtrain,1).values.view(-1,1), torch.quantile(Xtrain, 0.25, dim=1, keepdim=True), torch.quantile(Xtrain, 0.75, dim=1, keepdim=True), (Xtrain[:,torch.arange(4,22,2)] - Xtrain[:,torch.arange(2,20,2)]), (Xtrain[:,torch.arange(5,22,2)] - Xtrain[:,torch.arange(3,20,2)]) ) ,1)
- # polynomical features:
- pf = PolynomialFeatures(degree=2, interaction_only=False, include_bias=False)
- Xtrain = torch.from_numpy(pf.fit_transform(Xtrain)).float()
- print('feature synthesis done',flush=True)
- print('train shapes:')
- print(Xtrain)
- print(Xtrain.shape)
- if normalization=='minMax':
- mi,ma,r = torch.load('GNN_normalization_factors_minMax_{}.pt'.format(str(Xtrain.shape[1])) )
- Xtrain = (Xtrain - mi)/r
- Xtrain = Xtrain.float()
- return Xtrain
- def makeX2(X1, T2, Nodes):
- gen = [i.split('__', 1)[0] for i in T2]
- moa = [1 if i.split('__', 1)[1]=='act' else 2 for i in T2]
- X2 = np.zeros(len(Nodes))
- X2[[Nodes.index(i) for i in gen]] = moa
- X2 = np.tile(X2,(X1.shape[0],1))
- if len(X1.shape)==1: X1.shape=(1,X1.shape[0])
- if len(X2.shape)==1: X2.shape=(1,X2.shape[0])
- print(X1.shape); print(X2.shape)
- X1dim = X1.shape[1]
- X2dim = X2.shape[1]
- return X1, X2, X1dim
- class SimpleDataset(Dataset):
- def __init__(self, x, y):
- self.x = x
- self.y = y
- assert self.x.size(0) == self.y.size(0)
- def __len__(self):
- return self.x.size(0)
- def __getitem__(self, idx):
- return self.x[idx], self.y[idx]
- def prepareRealdataDataLoader(X1_file, DETF_file, removeColFile, normalization):
- directed_asymm_norm = True
- directed_set_diag = False
- directed_remove_diag = False
- num_propagations = 5
- num_node_features = 2
- num_hops = 11
- Nodes = open('nodeList_adjacency_PKN_sorted_GNN_nodeLabel.csv', 'r').read().splitlines()
- num_nodes = len(Nodes)
- # Graph
- PKN = np.loadtxt("adjacency_PKN.csv.gz", delimiter=",",dtype=Dtype)
- g = Graph.Weighted_Adjacency(PKN)
- PKN = PKN.T
- e = np.where(PKN!=0)
- edge_index = torch.cat((torch.unsqueeze(torch.from_numpy(e[0]),0), torch.unsqueeze(torch.from_numpy(e[1]),0) ), axis=0)
- np.sum(PKN[np.array(edge_index[0]),np.array(edge_index[1])]==0)
- # Reactome count
- RE = np.loadtxt("adjacency_Reactome_count.csv.gz", delimiter=",",dtype=Dtype)
- e2 = np.where(RE!=0)
- edge_index_RE = torch.cat((torch.unsqueeze(torch.from_numpy(e2[0]),0), torch.unsqueeze(torch.from_numpy(e2[1]),0) ), axis=0)
- np.sum(RE[np.array(edge_index_RE[0]),np.array(edge_index_RE[1])]==0)
- DETFs = []
- X1 = np.loadtxt(X1_file, delimiter=",")
- text_file = open(DETF_file, "r"); lines = text_file.read().split("\n"); DETFs = [l for l in lines if l!='']; DETFs = [x for x in DETFs if x.split("__")[0] in Nodes]
- ## graph embedding ##
- row, col = edge_index
- rowRE, colRE = edge_index_RE
- edgeRE = torch.tensor(RE[rowRE,colRE])
- adjRE = get_adj(rowRE, colRE, edgeRE, num_nodes, norm=False, asymm_norm=directed_asymm_norm, set_diag=directed_set_diag, remove_diag=directed_remove_diag) # only once
- I_TFs = open('TF_indices.csv', 'r').read().splitlines()
- I_TFs = [int(i) for i in I_TFs]
- col_to_remove = torch.load(removeColFile)
- X1, X2, num_nodes = makeX2(X1, DETFs, Nodes) # just 1 input statei
- tensor_X1 = torch.Tensor(X1)
- tensor_X2 = torch.Tensor(X2)
- # graph embedding
- gD = createGraphEmbedding(num_nodes, tensor_X1, tensor_X2, None, None, adjRE, num_propagations, g, edge_index, row, col, directed_asymm_norm, directed_set_diag, directed_remove_diag, I_TFs, num_hops)
- # expand, synthesize and normalize embedding
- Xdata = expandAndNormalizeEmbedding(gD, normalization)
- # remvoe columns
- Xdata = Xdata[:,~col_to_remove]
- Xdata = torch.nan_to_num(Xdata, nan=0.0)
- print(Xdata)
- print(Xdata.shape)
- # dataloader
- num_features = Xdata.shape[1]
- query_dataset = SimpleDataset(Xdata, torch.zeros([Xdata.shape[0]])) # dummy class
- query_loader = DataLoader(query_dataset, batch_size=1000000, shuffle=False)
- return(query_loader)
- def createGraphEmbedding(num_nodes, tensor_X1, tensor_X2, tensor_y, tensor_y2, adjRE, num_propagations, g, edge_index, row, col, directed_asymm_norm, directed_set_diag, directed_remove_diag, I_TFs, num_hops):
- all_idx = torch.tensor([i for i in range(num_nodes)])
- gD = []
- for i in range(tensor_X1.shape[0]):
- op_dict = {}
- batch_x = torch.stack([tensor_X1[i], tensor_X2[i]],dim=1)
- batch_x2 = torch.stack([tensor_X1[i], tensor_X2[i]],dim=1) # for opposite direction
- edge_multiple = batch_x[:,0][np.array(edge_index[0])] * batch_x[:,0][np.array(edge_index[1])]
- edge_multiple = edge_multiple / edge_multiple.max().item() # scaling
- adj = get_adj(row, col, edge_multiple, num_nodes, norm=False, asymm_norm=directed_asymm_norm, set_diag=directed_set_diag, remove_diag=directed_remove_diag)
- adj2 = get_adj(row, col, edge_multiple, num_nodes, norm=False, asymm_norm=directed_asymm_norm, set_diag=directed_set_diag, remove_diag=directed_remove_diag)
- # For reactome network
- absoluteDEG = torch.abs(batch_x[:,1])
- # For shortest paths
- g.es['weight'] = np.round(np.array(1 / (edge_multiple + 1)),3) # inverse edge weights for shortest paths
- degs = torch.where(batch_x[:,1]!=0)[0].tolist()
- detfs = list(set(I_TFs) & set(degs))
- if len(detfs)==0:
- detfs = degs
- elif len(detfs) > 10:
- detfs = np.array(detfs)[np.random.randint(0,len(detfs),10)].tolist()
- if tensor_y==None:
- op_dict['label'] = []
- else:
- op_dict['label'] = torch.stack([tensor_y[i].to(torch.long), tensor_y2[i].to(torch.long)],dim=1)
- op_dict['op_embedding'] = []
- op_dict['op_embedding'].append(batch_x[all_idx].to(torch.float))
- print('Diffusing node features')
- for _ in tqdm(range(num_propagations)):
- # 1. forward
- batch_x = adj @ batch_x
- op_dict['op_embedding'].append(torch.from_numpy(batch_x[all_idx]))
- # 2. transposed
- batch_x2 = adj2 @ batch_x2
- op_dict['op_embedding'].append(torch.from_numpy(batch_x2[all_idx]))
- # Reactome overlap
- op_dict['properties'] = []
- op_dict['properties'].append(torch.tensor(adjRE @ absoluteDEG).reshape(-1,1) )
- # network properties
- op_dict['properties'].append(torch.tensor(g.strength(weights=g.es['weight'], mode='in')).reshape(-1,1) )
- op_dict['properties'].append(torch.tensor(g.strength(weights=g.es['weight'], mode='out')).reshape(-1,1) )
- print("Computing shortest paths to DETFs...")
- s = g.shortest_paths(source=detfs, target=None,weights=g.es['weight'], mode='in') # the heavier the longer
- s = np.array(s)
- s[s==0] = 'inf'
- # reachability ratio (how many can each gene reach / total DEGs)
- rr = (s.shape[0] - np.sum(np.isinf(s),0)) / s.shape[0]
- op_dict['properties'].append(torch.tensor(rr.reshape(-1,1)))
- # average SP to DEGs
- s[np.isinf(s)] = 100
- op_dict['properties'].append(torch.tensor(np.mean(s,0).reshape(-1,1)))
- # Louvain community label
- g_undirected = g.as_undirected()
- g_undirected.es['weight'] = (torch.abs(edge_multiple) + 1e-6).cpu().numpy()
- community_labels = g_undirected.community_multilevel(weights=g_undirected.es['weight']).membership
- op_dict['properties'].append(torch.tensor(community_labels).reshape(-1, 1))
- # Nonlinear Transformations
- signed_log_transformed = torch.sign(op_dict['op_embedding'][-1]) * torch.log(torch.abs(op_dict['op_embedding'][-1]) + 1e-6) # signed Log
- sqrt_features = torch.sqrt(torch.clamp(op_dict['op_embedding'][-1], min=0)) # Square root
- quadrart_features = torch.pow(torch.clamp(op_dict['op_embedding'][-1], min=0), 1/4)
- op_dict['op_embedding'].extend([signed_log_transformed, sqrt_features, quadrart_features])
- # Pseudo-Temporal Analysis
- temporal_change = [ op_dict['op_embedding'][t] - op_dict['op_embedding'][t-1] for t in range(1, num_hops-1) ]
- op_dict['op_embedding'].extend(temporal_change)
- # Node Similarity
- cos_sim = F.cosine_similarity(torch.tensor(batch_x).unsqueeze(0), torch.tensor(batch_x).unsqueeze(1), dim=2)
- del batch_x, batch_x2
- op_dict['properties'].append(cos_sim.mean(dim=1).reshape(-1, 1))
- gD.append(op_dict)
- return gD
- class VAE_decrease(nn.Module):
- def __init__(self, in_channels, initial_hidden_size, num_layers, dropout, latent_dimension):
- super(VAE_decrease, self).__init__()
- # Encoder Layer Sizes (decreasing)
- self.encoder_sizes = self._generate_hidden_sizes(initial_hidden_size, num_layers, decreasing=True)
- self.decoder_sizes = self.encoder_sizes[::-1] # Decoder Layer Sizes (reverse of encoder)
- # Encoder
- self.encoder_layers = nn.ModuleList()
- prev_channels = in_channels
- for hidden_size in self.encoder_sizes:
- self.encoder_layers.append(self._build_layer(prev_channels, hidden_size, dropout, batch_norm=True))
- prev_channels = hidden_size
- self.fc_mu = nn.Linear(self.encoder_sizes[-1], latent_dimension)
- self.fc_logvar = nn.Linear(self.encoder_sizes[-1], latent_dimension)
- # Decoder
- self.decoder_layers = nn.ModuleList()
- prev_channels = latent_dimension
- for hidden_size in self.decoder_sizes:
- self.decoder_layers.append(self._build_layer(prev_channels, hidden_size, dropout, batch_norm=False)) # No BatchNorm in decoder
- prev_channels = hidden_size
- # Final output layer
- self.output_layer = nn.Linear(self.decoder_sizes[-1], in_channels)
- def _generate_hidden_sizes(self, initial_size, num_layers, decreasing=True):
- """Generate hidden layer sizes with an intermediate non-power-of-2 layer, then powers of 2"""
- sizes = [initial_size]
- # Find the largest power of 2 smaller than initial_size
- largest_power_of_2 = 2 ** (initial_size.bit_length() - 1)
- if largest_power_of_2 == initial_size:
- largest_power_of_2 //= 2 # If it's already a power of 2, go one step lower
- # Add the largest power of 2
- sizes.append(largest_power_of_2)
- # Progressively decrease in powers of 2
- size = largest_power_of_2 // 2
- for _ in range(num_layers - 2):
- sizes.append(size)
- size = max(size // 2, 8) # Ensure it doesn't go below 8
- return sizes
- def _build_layer(self, in_features, out_features, dropout, batch_norm):
- """Creates a single fully connected layer with activation & dropout"""
- layers = [nn.Linear(in_features, out_features)]
- if batch_norm:
- layers.append(nn.BatchNorm1d(out_features))
- layers.append(nn.ReLU())
- if dropout > 0:
- layers.append(nn.Dropout(dropout))
- return nn.Sequential(*layers)
- def encode(self, x):
- for layer in self.encoder_layers:
- x = layer(x)
- mu = self.fc_mu(x)
- logvar = self.fc_logvar(x)
- logvar = torch.clamp(logvar, min=-5, max=5) # Prevent extreme variance collapse
- return mu, logvar
- def reparameterize(self, mu, logvar):
- std = torch.exp(0.5 * logvar)
- eps = torch.randn_like(std)
- return mu + eps * std
- def decode(self, z):
- for layer in self.decoder_layers:
- z = layer(z)
- return torch.sigmoid(self.output_layer(z)) # Output constrained to [0,1]
- def forward(self, x):
- mu, logvar = self.encode(x)
- z = self.reparameterize(mu, logvar)
- return self.decode(z), mu, logvar
- def evaluate_ffn(y_true, y_pred):
- # Convert tensors to numpy if needed
- y_true = y_true.cpu().numpy() if isinstance(y_true, torch.Tensor) else np.array(y_true)
- y_pred = y_pred.cpu().numpy() if isinstance(y_pred, torch.Tensor) else np.array(y_pred)
- # Handle Class 3 as wildcard: prediction of 1 or 2 is considered correct
- y_true_adj = y_true.copy()
- class3_mask = y_true == 3
- if np.any(class3_mask):
- correct_mask = class3_mask & ((y_pred == 1) | (y_pred == 2))
- y_true_adj[correct_mask] = y_pred[correct_mask]
- y_true_adj[class3_mask & (y_pred == 0)] = -1 # Mark as invalid
- # Filter out invalid labels (-1)
- valid_mask = y_true_adj != -1
- y_true_adj = y_true_adj[valid_mask]
- y_pred = y_pred[valid_mask]
- # Derived stats
- labels = np.unique(np.concatenate((y_true_adj, y_pred)))
- cm = confusion_matrix(y_true_adj, y_pred, labels=labels)
- TP = np.diag(cm).sum()
- FP = cm.sum(axis=0).sum() - TP
- FN = cm.sum(axis=1).sum() - TP
- TN = cm.sum() - (TP + FP + FN)
- precision = TP / (TP + FP + 1e-8)
- recall = TP / (TP + FN + 1e-8)
- f1 = 2 * precision * recall / (precision + recall + 1e-8)
- fpr = FP / (FP + TN + 1e-8)
- fnr = FN / (FN + TP + 1e-8)
- return {"f1": f1, "fpr": fpr, "fnr": fnr}
- class FFN_decay(pl.LightningModule):
- def __init__(self, in_channels, initial_size, out_channels, num_layers, dropout=0.0, decay_factor=0.7, lr=0.005, l1_lambda=0.0, l2_lambda=0.0):
- super(FFN_decay, self).__init__()
- self.save_hyperparameters()
- self.hidden_layer_sizes = self._generate_hidden_sizes(num_layers, initial_size, decay_factor)
- self.layers = nn.ModuleList()
- prev_channels = in_channels
- for hidden_channels in self.hidden_layer_sizes:
- self.layers.append(nn.Sequential(
- nn.Linear(prev_channels, hidden_channels),
- nn.BatchNorm1d(hidden_channels, momentum=0.9),
- nn.LeakyReLU(),
- nn.Dropout(dropout)
- ))
- prev_channels = hidden_channels
- self.layers.append(nn.Linear(prev_channels, out_channels))
- self.lr = lr
- self.l1_lambda = l1_lambda
- self.l2_lambda = l2_lambda
- self.best_val_metrics = {"f1": 0, "fpr": 1, "fnr": 1}
- self.best_test_metrics = {"f1": 0, "fpr": 1, "fnr": 1}
- def _generate_hidden_sizes(self, num_layers, initial_size, decay_factor=0.7):
- hidden_sizes = []
- size = initial_size
- for _ in range(num_layers):
- hidden_sizes.append(size)
- size = max(int(size * decay_factor), 8)
- return hidden_sizes
- def reset_parameters(self):
- for layer in self.layers:
- if isinstance(layer, nn.Sequential):
- for sub_layer in layer:
- if hasattr(sub_layer, 'reset_parameters'):
- sub_layer.reset_parameters()
- elif hasattr(layer, 'reset_parameters'):
- layer.reset_parameters()
- def forward(self, x):
- for layer in self.layers[:-1]:
- x = layer(x)
- return self.layers[-1](x)
- def step(self, batch, mode="train"):
- x, labels = batch
- logits = self(x)
- loss = F.cross_entropy(logits, labels, reduction='mean')
- if self.l1_lambda > 0:
- l1_penalty = sum(p.abs().sum() for p in self.parameters() if p.requires_grad)
- loss = loss + self.l1_lambda * l1_penalty
- preds = logits.argmax(dim=1)
- eval_results = evaluate_ffn(labels, preds) # Updated function
- self.log_dict({
- f"{mode}_loss": loss.item(),
- f"{mode}_f1": eval_results["f1"],
- f"{mode}_fpr": eval_results["fpr"],
- f"{mode}_fnr": eval_results["fnr"]
- }, sync_dist=True, prog_bar=True)
- return loss, eval_results
- def training_step(self, batch, batch_idx):
- loss, eval_results = self.step(batch, mode="train")
- total_batches = self.trainer.num_training_batches
- print(f"Batch {batch_idx+1}/{total_batches}: Loss = {loss.item():.6f}", end="\r")
- return loss
- def validation_step(self, batch, batch_idx, dataloader_idx=0):
- loss, eval_results = self.step(batch, mode="val")
- return {"loss": loss, **eval_results, "dataloader_idx": dataloader_idx}
- def validation_epoch_end(self, outputs):
- if isinstance(outputs[0], list):
- val_outputs = outputs[0]
- test_outputs = outputs[1] if len(outputs) > 1 else []
- else:
- val_outputs, test_outputs = outputs, []
- def aggregate_metrics(output_list, prefix):
- if len(output_list) == 0:
- return {f"{prefix}_{key}": 0 for key in ["f1", "fpr", "fnr"]}
- return {
- f"{prefix}_{key}": torch.tensor([float(x[key]) for x in output_list], dtype=torch.float32).mean().item()
- for key in ["f1", "fpr", "fnr"]
- }
- val_metrics = aggregate_metrics(val_outputs, "val")
- test_metrics = aggregate_metrics(test_outputs, "test")
- self.log_dict(val_metrics, sync_dist=True, prog_bar=True)
- self.log_dict(test_metrics, sync_dist=True, prog_bar=True)
- train_metrics = {key: self.trainer.logged_metrics.get(f"train_{key}", 0) for key in ["f1", "fpr", "fnr"]}
- val_metrics = {key: val_metrics.get(f"val_{key}", 0) for key in ["f1", "fpr", "fnr"]}
- test_metrics = {key: test_metrics.get(f"test_{key}", 0) for key in ["f1", "fpr", "fnr"]}
- print(f"Epoch {self.current_epoch}: "
- f"train_f1 = {train_metrics['f1']:.4f}, fpr = {train_metrics['fpr']:.4f}, fnr = {train_metrics['fnr']:.4f} | "
- f"val_f1 = {val_metrics['f1']:.4f}, fpr = {val_metrics['fpr']:.4f}, fnr = {val_metrics['fnr']:.4f} | "
- f"test_f1 = {test_metrics['f1']:.4f}, fpr = {test_metrics['fpr']:.4f}, fnr = {test_metrics['fnr']:.4f}")
- def configure_optimizers(self):
- optimizer = torch.optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.l2_lambda)
- scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
- return {"optimizer": optimizer, "lr_scheduler": scheduler}
- def exportPredictedProteins(outfile, sigPs):
- with open(outfile,'w') as wr:
- for i in range(len(sigPs)):
- wr.write(sigPs[i] +"\n")
- def wait_for_file(directory, filename, interval=1, timeout=60):
- file_path = os.path.join(directory, filename)
- # Pre-check if file already exists
- if os.path.exists(file_path):
- #print(f"File {filename} already exists")
- return True
- #print(f"Waiting for {filename} to appear in {directory}...")
- start_time = time.time()
- while time.time() - start_time < timeout:
- if os.path.exists(file_path):
- #print(f"File {filename} detected")
- return True
- time.sleep(interval)
- print("Timeout: File did not appear.")
- return False
- def compute_BES_score(pemp, hypergeo_pval, rank_hy, pi0=0.99, w_posterior=20.0, w_hypergeo=1.0, w_hypergeo_rank=1.0, pemp_penalty_multiplier=1.0):
- # Convert to arrays
- pemp = np.array(pemp, dtype=float)
- hypergeo_pval = np.array(hypergeo_pval, dtype=float)
- rank_hy = np.array(rank_hy, dtype=float)
- # Identify invalid scores: zero jaccard or hypergeo p = 1 (not 0!)
- mask_zero = hypergeo_pval == 1.0
- # Posterior and penalty
- posterior = 1 - pi0 * pemp
- pemp_soft_penalty = np.exp(-pemp_penalty_multiplier * (1 - pemp))
- s_posterior = posterior * pemp_soft_penalty
- # Normalized rank components (1 = best)
- rank_hy_norm = 1 - (rank_hy - np.min(rank_hy)) / (np.max(rank_hy) - np.min(rank_hy) + 1e-8)
- # BES score
- BES = (w_posterior * s_posterior + w_hypergeo * (1 - hypergeo_pval) + w_hypergeo_rank * rank_hy_norm)
- # Apply hard zero override
- BES[mask_zero] = 0.0
- # Ranking: higher score = better
- BES_rank = pd.Series(BES).rank(ascending=False, method='min').values
- BES_rank[BES == 0.0] = len(BES)
- return BES, BES_rank
model_library.py at commit b0cfe8b, no license · at the source
Overview
and 20 other authors
Justin Donnelly13, Kasey Woleben19, Francesc Xavier Soriano20, Jose C. Fernandez-Checa21,22,23,24, Natascia Ventura15,25,26, Sidney Cambridge27, Ertan Mayatepek1, Antonella Spinazzola28, Markus Schuelke29,30, Nikolaus Rajewsky8,9,30,31,32,33, Andrea Rossi34, Alex Peralvarez-Marin12, Felix Distelmaier1, Ethan Perlstein14, Ian J. Holt7,35,36,37, Emma Puighermanal38, Ole Pless6, Christine R. Rose5, Antonio Del Sol3,35,39, Alessandro Prigione139 affiliations
- Department of General Pediatrics, Neonatology and Pediatric Cardiology, Medical Faculty and University Hospital Düsseldorf, Heinrich Heine University Düsseldorf,Düsseldorf, Germany
- Faculty of Mathematics and Natural Sciences, Heinrich Heine University Düsseldorf,Düsseldorf, Germany
- Computational Biology Group, Luxembourg Centre for Systems Biomedicine, University of Luxembourg,Esch-sur-Alzette, Luxembourg
- University of Pittsburgh School of Medicine, Vascular Medicine Institute,Pittsburgh, PA USA
- Institute of Neurobiology, Heinrich Heine University,Düsseldorf, Germany
- Fraunhofer Institute for Translational Medicine and Pharmacology ITMP, Discovery Research ScreeningPort,Hamburg, Germany
- Department of Neurosciences, Biogipuzkoa Health Research Institute,San Sebastian, Spain
- Max Delbrück Center for Molecular Medicine in the Helmholtz Association (MDC),Berlin, Germany
- Berlin Institute for Medical Systems Biology (BIMSB), Berlin, Germany
- Neuropsychiatry and Laboratory of Molecular Psychiatry, Department of Psychiatry and Neurosciences, Charité—Universitätsmedizin,Berlin, Germany
- Department of Molecular Biology, Institute of Genetics and Animal Biotechnology, Polish Academy of Sciences,Jastrzebiec n/Warsaw, Poland
- Unit of Biophysics, Department of Biochemistry and Molecular Biology Institute of Neurosciences, Universitat Autònoma de Barcelona,Barcelona, Spain
- Charité—Universitätsmedizin,Berlin, Germany
- Perlara PBC, Vancouver, WA USA
- Institute of Cell Biology, Heinrich Heine University,Düsseldorf, Germany
- Present Address: Axol Bioscience Ltd, Berlin, Germany
- Cluster of Excellence Cellular Stress Responses in Aging-associated Diseases (CECAD), Faculty of Medicine and University Hospital of Cologne, University of Cologne,Cologne, Germany
- Present Address: Centogene GmbH,Rostock, Germany
- Cure Mito Foundation, McKinney, TX USA
- Celltec-UB, Departament de Biologia Cellular, Fisiologia i Immunologia, Institut de Neurociències, Universitat de Barcelona,Barcelona, Spain
- Department of Molecular and Cellular Biomedicine, Institute of Biomedical Research of Barcelona (IIBB), CSIC,Barcelona, Spain
- Liver Unit, Hospital Clinic i Provincial de Barcelona, Institut d’Investigacions Biomèdiques August Pi i Sunyer (IDIBAPS),Barcelona, Spain
- Centro de Investigación Biomédica en Red (CIBEREHD),Barcelona, Spain
- Department of Medicine, Keck School of Medicine, University of Southern California,Los Angeles, CA USA
- IUF-Leibniz Research, Institute for Environmental Medicine,Düsseldorf, Germany
- Dept. for the Promotion of Human Science and Quality of Life, San Raffaele University of Rome,Rome, Italy
- Institute of Physiological Chemistry, University Medical Center of the Johannes Gutenberg University,Mainz, Germany
- Department of Clinical and Movement Neurosciences, UCL Queen Square Institute of Neurology, Royal Free Campus,London, UK
- Department of Neuropediatrics, Charité–Universitätsmedizin,Berlin, Germany
- NeuroCure Cluster of Excellence,Berlin, Germany
- German Center for Cardiovascular Research (DZHK),Berlin, Germany
- National Center for Tumor Diseases (NCT), German Cancer Consortium (DKTK),Berlin, Germany
- German Center for Neurodegenerative Diseases (DZNE),Berlin, Germany
- Genome Engineering and Model Development lab (GEMD), IUF-Leibniz Research, Institute for Environmental Medicine,Düsseldorf, Germany
- IKERBASQUE, Basque Foundation for Science,Bilbao, Spain
- University of the Basque Country-Bizkaia Campus,Bilbao, Spain
- CIBERNED (Center for Networked Biomedical Research on Neurodegenerative Diseases) Ministry of Economy and Competitiveness, Institute Carlos III),Madrid, Spain
- Neuroscience Institute, Department of Cell Biology, Physiology and Immunology, Autonomous University of Barcelona,Bellaterra, Spain
- CIC bioGUNE-BRTA (Basque Research and Technology Alliance), Bizkaia Technology Park,Derio, Spain
Abstract
Leigh syndrome (Leigh) is an untreatable mitochondrial disorder characterized by lactic acidosis and basal ganglia and midbrain pathology, leading to psychomotor regression and early death. We previously uncovered impaired neuronal morphogenesis in Leigh cerebral organoids carrying SURF1 gene variants. Leveraging this phenotype, we here develop a deep learning algorithm tailored for cell type-specific drug repurposing screening. In parallel, we perform a survival drug screen in a yeast model of Leigh. The two approaches independently converge on azole compounds, two of which - talarozole and sertaconazole - rescue neuronal morphogenesis in Leigh neurons and lower lactate release and improve growth rate in Leigh midbrain organoids. Mechanistically, these compounds modulate the retinoic acid pathway and membrane-associate lipid metabolism. The findings highlight azoles as promising candidates for Leigh and demonstrate the potential of combining in silico screens with human brain organoids as new approach methodologies (NAMs) to advance the discovery of therapeutics addressing rare neurodevelopmental disorders.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.
osb-codes/dl_drug_repurposing
b0cfe8b88e7e46c7c1f4bee21f416f6e30cfbb9f, 17 December 2025Availability: 1 check, the latest on 29 September 2026: the link answers
- 29 September 2026: the link answers
9 files
- Py_launcher_make_embeddi
ngs.sh , Shell, 13 lines - Py_launcher_run_FFNs.sh, Shell, 22 lines
- R_launcher_compute_drug_
pvalues.sh , Shell, 10 lines - compute_drug_pvalues.R, R, 99 lines
- make_embeddings.py, Python, 17 lines
- model_library.py, Python, 457 lines, 4 matches
- rank_drugs_by_BES.py, Python, 47 lines
- run_FFNs.py, Python, 131 lines
- README.md, Text, 130 lines
Code availability
The program code and input files are available at: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 8 scripts, each with its path and the digest of its content;
- 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
Datasets cited
- bioproject:PRJNA1378526, at NCBI BioProject; found in “Data availability”
- geo:GSE152915, at NCBI GEO; found in “Data availability”
Data availability
There are restrictions to the availability of patient-derived iPSCs due to our ethical approval that does not support sharing with third parties without a specific amendment and does not allow performing genomic studies to respect the European privacy protection law.
Single-cell RNA sequencing (scRNAseq) data are deposited in the Sequence Read Archive (SRA) with bioproject number PRJNA1378526 and can be downloaded at: https://
Additional scRNAseq data used in this study have been previously deposited in the Gene Expression Omnibus (GEO) database: GSE152915 (https://
Lipidomics data are deposited in Metabolomics Workbench repository151 with project number PR002944 and can be downloaded at: 10.21228/
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 29 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 40 authors, 3 keywords, 12 MeSH terms, 16 funders, 150 references.
Cite
This paper
Menacho, C., Okawa, S., Álvarez-Merz, I., Wittich, A., Muñoz-Oreja, M., Lisowski, P., Martín, M. L., Pentimalli, T. M., Zakin, S., Thevandavakkam, M., Jerred, C., Lickfett, S., Petersilie, L., Rybak-Wolf, A., Seibt, A., Herebian, D., Inak, G., Brodesser, S., Zaliani, A., . . . Prigione, A. (2026). Accelerating Leigh syndrome drug discovery through deep learning screening in brain organoids. Nature communications, 17(1), 3570. https://
BibTeX
@article{menacho2026acce
author = {Menacho, Carmen and Okawa, Satoshi and Álvarez-Merz, Iris and Wittich, Annika and Muñoz-Oreja, Mikel and Lisowski, Pawel and Martín, Mario López and Pentimalli, Tancredi Massimo and Zakin, Shiri and Thevandavakkam, Mathuravani and Jerred, Caleb and Lickfett, Selene and Petersilie, Laura and Rybak-Wolf, Agnieszka and Seibt, Annette and Herebian, Diran and Inak, Gizem and Brodesser, Susanne and Zaliani, Andrea and Mlody, Barbara and Donnelly, Justin and Woleben, Kasey and Soriano, Francesc Xavier and Fernandez-Checa, Jose C. and Ventura, Natascia and Cambridge, Sidney and Mayatepek, Ertan and Spinazzola, Antonella and Schuelke, Markus and Rajewsky, Nikolaus and Rossi, Andrea and Peralvarez-Marin, Alex and Distelmaier, Felix and Perlstein, Ethan and Holt, Ian J. and Puighermanal, Emma and Pless, Ole and Rose, Christine R. and Del Sol, Antonio and Prigione, Alessandro},
title = {{Accelerating Leigh syndrome drug discovery through deep learning screening in brain organoids}},
journal = {Nature communications},
year = {2026},
month = apr,
volume = {17},
number = {1},
pages = {3570},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/
url = {https://
pmid = {42009687},
pmcid = {PMC13096141}
}
RIS
TY - JOUR
AU - Menacho, Carmen
AU - Okawa, Satoshi
AU - Álvarez-Merz, Iris
AU - Wittich, Annika
AU - Muñoz-Oreja, Mikel
AU - Lisowski, Pawel
AU - Martín, Mario López
AU - Pentimalli, Tancredi Massimo
AU - Zakin, Shiri
AU - Thevandavakkam, Mathuravani
AU - Jerred, Caleb
AU - Lickfett, Selene
AU - Petersilie, Laura
AU - Rybak-Wolf, Agnieszka
AU - Seibt, Annette
AU - Herebian, Diran
AU - Inak, Gizem
AU - Brodesser, Susanne
AU - Zaliani, Andrea
AU - Mlody, Barbara
AU - Donnelly, Justin
AU - Woleben, Kasey
AU - Soriano, Francesc Xavier
AU - Fernandez-Checa, Jose C.
AU - Ventura, Natascia
AU - Cambridge, Sidney
AU - Mayatepek, Ertan
AU - Spinazzola, Antonella
AU - Schuelke, Markus
AU - Rajewsky, Nikolaus
AU - Rossi, Andrea
AU - Peralvarez-Marin, Alex
AU - Distelmaier, Felix
AU - Perlstein, Ethan
AU - Holt, Ian J.
AU - Puighermanal, Emma
AU - Pless, Ole
AU - Rose, Christine R.
AU - Del Sol, Antonio
AU - Prigione, Alessandro
TI - Accelerating Leigh syndrome drug discovery through deep learning screening in brain organoids
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/
VL - 17
IS - 1
SP - 3570
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Accelerating Leigh syndrome drug discovery through deep learning screening in brain organoids",
"container-title": "Nature communications",
"author": [
{
"family": "Menacho",
"given": "Carmen"
},
{
"family": "Okawa",
"given": "Satoshi"
},
{
"family": "Álvarez-Merz",
"given": "Iris"
},
{
"family": "Wittich",
"given": "Annika"
},
{
"family": "Muñoz-Oreja",
"given": "Mikel"
},
{
"family": "Lisowski",
"given": "Pawel"
},
{
"family": "Martín",
"given": "Mario López"
},
{
"family": "Pentimalli",
"given": "Tancredi Massimo"
},
{
"family": "Zakin",
"given": "Shiri"
},
{
"family": "Thevandavakkam",
"given": "Mathuravani"
},
{
"family": "Jerred",
"given": "Caleb"
},
{
"family": "Lickfett",
"given": "Selene"
},
{
"family": "Petersilie",
"given": "Laura"
},
{
"family": "Rybak-Wolf",
"given": "Agnieszka"
},
{
"family": "Seibt",
"given": "Annette"
},
{
"family": "Herebian",
"given": "Diran"
},
{
"family": "Inak",
"given": "Gizem"
},
{
"family": "Brodesser",
"given": "Susanne"
},
{
"family": "Zaliani",
"given": "Andrea"
},
{
"family": "Mlody",
"given": "Barbara"
},
{
"family": "Donnelly",
"given": "Justin"
},
{
"family": "Woleben",
"given": "Kasey"
},
{
"family": "Soriano",
"given": "Francesc Xavier"
},
{
"family": "Fernandez-Checa",
"given": "Jose C."
},
{
"family": "Ventura",
"given": "Natascia"
},
{
"family": "Cambridge",
"given": "Sidney"
},
{
"family": "Mayatepek",
"given": "Ertan"
},
{
"family": "Spinazzola",
"given": "Antonella"
},
{
"family": "Schuelke",
"given": "Markus"
},
{
"family": "Rajewsky",
"given": "Nikolaus"
},
{
"family": "Rossi",
"given": "Andrea"
},
{
"family": "Peralvarez-Marin",
"given": "Alex"
},
{
"family": "Distelmaier",
"given": "Felix"
},
{
"family": "Perlstein",
"given": "Ethan"
},
{
"family": "Holt",
"given": "Ian J."
},
{
"family": "Puighermanal",
"given": "Emma"
},
{
"family": "Pless",
"given": "Ole"
},
{
"family": "Rose",
"given": "Christine R."
},
{
"family": "Del Sol",
"given": "Antonio"
},
{
"family": "Prigione",
"given": "Alessandro"
}
],
"container-title-short":
"volume": "17",
"issue": "1",
"page": "3570",
"DOI": "10.1038/
"PMID": "42009687",
"PMCID": "PMC13096141",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
20
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1016/j.xcrm.2026.102766 [code]
- A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.Journal: Cell reports. MedicineIn common: UMAP, igraph, TensorFlow, 5 other tools, 4 references
- [2] doi:10.1038/s44320-026-00208-7 [code]
- Interpretable deep generative ensemble learning for single-cell omics with Hydra.Journal: Molecular systems biologyIn common: UMAP, igraph, TensorFlow, 6 other tools, 2 references
- [3] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: PyTorch Lightning, UMAP, igraph, 7 other tools
- [4] doi:10.1021/acs.biochem.5c00596 [code]
- Cargo Recognition of Nesprin-2 by the Dynein Adapter Bicaudal D2 for a Nuclear Positioning Pathway That Is Important for Brain Development.Journal: BiochemistryIn common: PyTorch Lightning, PyTorch Geometric, TensorFlow, 6 other tools, 1 reference
- [5] doi:10.1038/s42003-026-10259-z [code]
- Spatial transcriptomic profiling of developing mouse hearts reveals a spatially patterned signaling environment.Journal: Communications biologyIn common: PyTorch Lightning, UMAP, PyTorch, 5 other tools, 2 references
- [6] doi:10.1038/s41598-026-53415-5 [code]
- Computational design and immunoinformatics validation of a T cell multi-epitope vaccine targeting glioblastoma stem cells.Journal: Scientific reportsIn common: PyTorch Lightning, PyTorch Geometric, TensorFlow, 6 other tools
- [7] doi:10.1038/s41586-026-10391-0 [code]
- Cell-type-targeted mitochondrial transplantation rescues cell degeneration.Journal: NatureIn common: PyTorch Lightning, PyTorch Geometric, TensorFlow, 6 other tools
- [8] doi:10.3390/ijms27156614 [code]
- Candidalysin Inhibits &
lt;i& gt;Porphyromonas gingivalis& lt;/ i& gt; Lipoprotein-Induced IL-1β Production in BV-2 Microglia via Hydrophobic Microbial Interactions. Journal: International journal of molecular sciencesIn common: PyTorch Lightning, PyTorch Geometric, TensorFlow, 6 other tools - [9] doi:10.1016/j.xcrm.2026.102651 [code]
- Integrative CSF profiling identifies disease-specific immune responses in leptomeningeal disease.Journal: Cell reports. MedicineIn common: PyTorch Lightning, UMAP, igraph, 6 other tools
- [10] doi:10.1016/j.isci.2026.116055 [code]
- Mapping the transcriptional diversity of calcium signaling in the mouse and human brain.Journal: iScienceIn common: PyTorch Geometric, UMAP, igraph, 6 other tools
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 8 scripts, and 4 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:baaece21c50e3f30…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
