Skip to content

Commit

Permalink
Merge pull request #291 from Steinbeck-Lab/feat-import-2D-3D-SDFs
Browse files Browse the repository at this point in the history
feat: export and import 2D / 3D SDFs
  • Loading branch information
CS76 authored Nov 28, 2024
2 parents a38339b + e9947a3 commit caf0c55
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 13 deletions.
78 changes: 78 additions & 0 deletions app/Console/Commands/Import2DSDFs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace App\Console\Commands;

use App\Models\Molecule;
use App\Models\Structure;
use DB;
use Illuminate\Console\Command;

class Import2DSDFs extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'coconut:import-2d-sdfs {file}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Imports 2D SDFs from a JSON file into Structures table';

/**
* Execute the console command.
*/
public function handle()
{
$file = storage_path($this->argument('file'));
if (! file_exists($file) || ! is_readable($file)) {
$this->error('File not found or not readable.');

return 1;
}

$batchSize = 1000;
$header = null;
$data = [];
$rowCount = 0;
$json = file_get_contents($file);
$json_data = json_decode($json, true);
if ($json === false) {
exit('Error reading the JSON file');
}

$batchSize = 10000; // Number of molecules to process in each batch
$data = []; // Array to store data for batch updating
$totalElements = count($json_data);

for ($i = 0; $i < $totalElements; $i += $batchSize) {
$this->info('Processing batch '.($i / $batchSize + 1).' of '.ceil($totalElements / $batchSize));
$batch = array_slice($json_data, $i, $totalElements - $i < $batchSize ? $totalElements - $i : $batchSize);
$this->insertBatch($batch);
}

$this->info('Annotation scores generated successfully.');
}

/**
* Update a batch of data into the database.
*
* @return void
*/
private function insertBatch(array $data)
{
DB::transaction(function () use ($data) {
foreach ($data as $identifier => $sdf_2d) {
$molecule = Molecule::select('id')->where('identifier', $identifier)->first();
$structure = new Structure;
$structure['molecule_id'] = $molecule->id;
$structure['2d'] = json_encode($sdf_2d);
$structure->save();
}
});
}
}
87 changes: 87 additions & 0 deletions app/Console/Commands/Import3DSDFs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace App\Console\Commands;

use App\Models\Molecule;
use DB;
use Illuminate\Console\Command;

class Import3DSDFs extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'coconut:import-3d-sdfs {file}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Imports 3D SDFs from multiple JSON file into Structures table';

/**
* Execute the console command.
*/
public function handle()
{
$batchSize = 10000; // Number of molecules to process in each batch

$file = storage_path($this->argument('file'));
$file_suffix = (int) $file[strpos($file, '.json') - 1];
$file_name_without_id = substr($file, 0, strpos($file, '.json') - 1);

// loop runs though all the files
for (; $file_suffix <= 18; $file_suffix++) {

$json = null;
$json_data = [];

$file_name = $file_name_without_id.$file_suffix.'.json';
$this->info('Starting loop for file '.$file_name);
if (! file_exists($file_name) || ! is_readable($file_name)) {
$this->error('File not found or not readable: '.$file_name);

return 1;
}

$json = file_get_contents($file_name);
if ($json === false) {
exit('Error reading the JSON file');
}
$this->info('File read');

$json_data = json_decode($json, true);
$keys = array_keys($json_data);
$this->info(end($keys));

$totalElements = count($json_data);
$this->info('Total elements: '.$totalElements);

for ($i = 0; $i < $totalElements; $i += $batchSize) {
$this->info('Processing batch '.($i / $batchSize + 1).' of '.ceil($totalElements / $batchSize));
$batch = array_slice($json_data, $i, $totalElements - $i < $batchSize ? $totalElements - $i : $batchSize);
$this->insertBatch($batch);
}
}
$this->info('Annotation scores generated successfully.');
}

/**
* Update a batch of data into the database.
*
* @return void
*/
private function insertBatch(array $data)
{
DB::transaction(function () use ($data) {
foreach ($data as $identifier => $sdf_3d) {
$structure = Molecule::where('identifier', $identifier)->first()->structures;
$structure['3d'] = json_encode($sdf_3d);
$structure->save();
}
});
}
}
13 changes: 8 additions & 5 deletions app/Livewire/MoleculeDepict2d.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

namespace App\Livewire;

use Illuminate\Support\Facades\Http;
use Livewire\Attributes\Computed;
use Livewire\Component;

class MoleculeDepict2d extends Component
{
public $molecule = null;

public $smiles = null;

public $name = null;
Expand Down Expand Up @@ -38,11 +39,13 @@ public function preview()

public function downloadMolFile($toolkit)
{
$response = Http::get(env('CM_API').'convert/mol2D?smiles='.urlencode($this->smiles).'&toolkit='.$toolkit);
$structureData = json_decode($this->molecule->structures->getAttributes()['2d'], true);

return response()->streamDownload(function () use ($response) {
echo $response->body();
}, $this->identifier.'.mol');
return response()->streamDownload(function () use ($structureData) {
echo $structureData;
}, $this->identifier.'.sdf', [
'Content-Type' => 'chemical/x-mdl-sdfile',
]);
}

public function render()
Expand Down
13 changes: 13 additions & 0 deletions app/Livewire/MoleculeDepict3d.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

class MoleculeDepict3d extends Component
{
public $molecule = null;

public $smiles = null;

public $height = 200;
Expand All @@ -21,6 +23,17 @@ public function source()
return env('CM_API').'depict/3D?smiles='.urlencode($this->smiles).'&height='.$this->height.'&width='.$this->width.'&CIP='.$this->CIP.'&toolkit=rdkit';
}

public function downloadSDFFile()
{
$structureData = json_decode($this->molecule->structures->getAttributes()['3d'], true);

return response()->streamDownload(function () use ($structureData) {
echo $structureData;
}, $this->molecule->identifier.'.sdf', [
'Content-Type' => 'chemical/x-mdl-sdfile',
]);
}

public function render()
{
return view('livewire.molecule-depict3d');
Expand Down
3 changes: 3 additions & 0 deletions app/Livewire/MoleculeHistoryTimeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public function getHistory()
{
$audit_data = [];
$audits_collection = $this->mol->audits->merge($this->mol->properties()->get()[0]->audits);
if ($this->mol->structures()->get()->count() > 0) {
$audits_collection = $audits_collection->merge($this->mol->structures()->get()[0]->audits);
}
foreach ($audits_collection->sortByDesc('created_at') as $index => $audit) {
$audit_data[$index]['user_name'] = $audit->getMetadata()['user_name'];
$audit_data[$index]['event'] = $audit->getMetadata()['audit_event'];
Expand Down
5 changes: 5 additions & 0 deletions app/Models/Molecule.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public function properties(): HasOne
return $this->hasOne(Properties::class);
}

public function structures(): HasOne
{
return $this->hasOne(Structure::class);
}

/**
* Get the variants associated with the molecule.
*/
Expand Down
17 changes: 17 additions & 0 deletions app/Models/Structure.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,21 @@ class Structure extends Model implements Auditable
{
use HasFactory;
use \OwenIt\Auditing\Auditable;

protected $fillable = [
'molecule_id',
'2d',
'3d',
'mol',
];

public function molecule()
{
return $this->belongsTo(Molecule::class, 'molecule_id');
}

public function transformAudit(array $data): array
{
return changeAudit($data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('structures', function (Blueprint $table) {
$table->foreignId('molecule_id')->unique()->constrained()->onDelete('cascade')->after('id');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('structures', function (Blueprint $table) {
$table->dropColumn(['molecule_id']);
});
}
};
9 changes: 5 additions & 4 deletions resources/views/livewire/molecule-depict2d.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div x-data="{ toolkit: @entangle('toolkit'), options: @entangle('options') }">
<fieldset x-show="options" class="px-4 w-full">
<fieldset x-show="options" class="pl-4 w-full">
<div class="mt-4 grid grid-cols-6 gap-y-6 gap-x-1 sm:grid-cols-6 sm:gap-x-4 items-center">
<label @click="toolkit= 'cdk'"
class="relative flex cursor-pointer rounded-lg bg-white p-3 focus:outline-none col-span-2">
Expand All @@ -8,7 +8,7 @@ class="relative flex cursor-pointer rounded-lg bg-white p-3 focus:outline-none c
<span class="flex flex-col">
<span class="block text-sm font-bold text-indigo-900">CDK</span>
</span>
</span>
</span>
<svg :class="{ 'text-indigo-400': toolkit == 'cdk', 'text-gray-400': toolkit == 'rdkit' }"
class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
Expand All @@ -34,8 +34,9 @@ class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<span class="pointer-events-none absolute -inset-px rounded-lg border-2" aria-hidden="true"></span>
</label>
<!-- Download Button -->
<span x-show="options" class="relative flex justify-center cursor-pointer" x-on:click="$wire.downloadMolFile(toolkit)" :title="`Download Mol file from ${toolkit}`" >
<svg width="25px" height="25px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<span x-show="options" class="relative flex justify-center cursor-pointer" x-on:click="$wire.downloadMolFile(toolkit)" :title="`Download SD file from RDKit`" >
2D
<svg class="ml-1" width="25px" height="25px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5535 16.5061C12.4114 16.6615 12.2106 16.75 12 16.75C11.7894 16.75 11.5886 16.6615 11.4465 16.5061L7.44648 12.1311C7.16698 11.8254 7.18822 11.351 7.49392 11.0715C7.79963 10.792 8.27402 10.8132 8.55352 11.1189L11.25 14.0682V3C11.25 2.58579 11.5858 2.25 12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V14.0682L15.4465 11.1189C15.726 10.8132 16.2004 10.792 16.5061 11.0715C16.8118 11.351 16.833 11.8254 16.5535 12.1311L12.5535 16.5061Z" fill="#1C274C" />
<path d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z" fill="#1C274C" />
</svg>
Expand Down
11 changes: 9 additions & 2 deletions resources/views/livewire/molecule-depict3d.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
<div class="w-full max-w-xl">
<div class="flex items-center justify-between" x-data="{ on: false }">
<span class="flex flex-grow flex-col">
<span class="text-sm font-medium leading-6 text-gray-900" id="availability-label">3D Structure</span>
<span class="text-sm text-gray-500" id="availability-description">Interactive JSMol molecular viewer
<span class="text-sm font-medium leading-6 text-gray-900" id="availability-label">3D Structure </span>
<span class="text-sm text-gray-500 pr-5" id="availability-description"><span class="text-sm text-gray-500">(by RDKit)</span> Interactive JSmol molecular viewer
</span>
</span>
<span class="relative flex justify-center cursor-pointer mr-4" x-on:click="$wire.downloadSDFFile()" :title="`Download SDF file from RDKit`" >
3D
<svg class="ml-1" width="25px" height="25px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5535 16.5061C12.4114 16.6615 12.2106 16.75 12 16.75C11.7894 16.75 11.5886 16.6615 11.4465 16.5061L7.44648 12.1311C7.16698 11.8254 7.18822 11.351 7.49392 11.0715C7.79963 10.792 8.27402 10.8132 8.55352 11.1189L11.25 14.0682V3C11.25 2.58579 11.5858 2.25 12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V14.0682L15.4465 11.1189C15.726 10.8132 16.2004 10.792 16.5061 11.0715C16.8118 11.351 16.833 11.8254 16.5535 12.1311L12.5535 16.5061Z" fill="#1C274C" />
<path d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z" fill="#1C274C" />
</svg>
</span>
<button @click="src = '{{ $this->source }}'"
:class="{ 'bg-gray-600': src=='', 'bg-green-600': src!='' }" type="button"
class="bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
Expand Down
4 changes: 2 additions & 2 deletions resources/views/livewire/molecule-details.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -934,11 +934,11 @@ class="ml-3 text-base text-gray-500">Lipinski
</section>
<section class="space-y-6 lg:col-span-1 lg:col-start-3 order-1 lg:order-2">
<div class="border aspect-h-2 aspect-w-3 overflow-hidden rounded-lg bg-white mb-2 mx-2">
<livewire:molecule-depict2d :height="300" :smiles="$molecule->canonical_smiles" :name="$molecule->name" :identifier="$molecule->identifier" :options="true"
<livewire:molecule-depict2d :height="300" :molecule="$molecule" :smiles="$molecule->canonical_smiles" :name="$molecule->name" :identifier="$molecule->identifier" :options="true"
lazy="on-load">
</div>
<div class="mx-2">
<livewire:molecule-depict3d :height="300" :smiles="$molecule->canonical_smiles" lazy="on-load">
<livewire:molecule-depict3d :height="300" :molecule="$molecule" :smiles="$molecule->canonical_smiles" lazy="on-load">
</div>
<dl class="mt-5 flex w-full mx-2">
<div class="md:text-left">
Expand Down

0 comments on commit caf0c55

Please sign in to comment.