Skip to content

Commit

Permalink
Merge branch 'main' into deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
tnaccarato committed Aug 13, 2024
2 parents 8c6a033 + 82fe39b commit cc9b7c7
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 96 deletions.
8 changes: 6 additions & 2 deletions vis_phewas/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,11 @@ def get(self, request):
print("Filters: ", filters)
# Decode the filters
filters = urllib.parse.unquote(filters)

# Get the SOM type from the request (disease or allele)
som_type = request.GET.get('type')
num_clusters = request.GET.get('num_clusters') or 5 if som_type == 'disease' else 7
print("Num Clusters: ", num_clusters)
print("SOM Type: ", som_type)
# Get the queryset and apply the filters
df = get_filtered_df(filters)
Expand All @@ -370,8 +373,9 @@ def get(self, request):
# Save the CSV content and SOM type to the temporary model
temp_data = TemporaryCSVData.objects.create(csv_content=csv_content, som_type=som_type)

# Return the ID of the temporary data in the response
return JsonResponse({'status': 'CSV data stored', 'data_id': temp_data.id})
# Return the ID of the temporary data in the response as well as the number of clusters
return JsonResponse({'status': 'CSV data stored', 'data_id': temp_data.id, 'num_clusters': num_clusters,
'filters': filters})

def cleanup_old_data(self):
threshold = timezone.now() - timedelta(days=1)
Expand Down
105 changes: 86 additions & 19 deletions vis_phewas/som/views.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import os
import urllib.parse
from collections import defaultdict
from datetime import datetime
from io import StringIO

from django.conf import settings
from django.shortcuts import render, get_object_or_404
import pandas as pd
from collections import defaultdict
from api.models import TemporaryCSVData
from rest_framework.views import APIView
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from minisom import MiniSom
import numpy as np
from sklearn.cluster import KMeans
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
from io import StringIO

from api.models import TemporaryCSVData
from django.conf import settings
from django.shortcuts import render, get_object_or_404
from mainapp.models import HlaPheWasCatalog
from minisom import MiniSom
from rest_framework.views import APIView
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler, OneHotEncoder


def cluster_results_to_csv(cluster_results):
Expand All @@ -40,9 +40,13 @@ class SOMSNPView(APIView):
def get(self, request):
# Get the data_id from the request (passed as a query parameter)
data_id = request.GET.get('data_id')
num_clusters = request.GET.get('num_clusters')
# Retrieve the temporary CSV data object using the data_id
temp_data = get_object_or_404(TemporaryCSVData, id=data_id)

if num_clusters is None:
num_clusters = 7

# Convert the CSV content to a DataFrame
csv_content = temp_data.csv_content
df = pd.read_csv(StringIO(csv_content))
Expand Down Expand Up @@ -107,7 +111,6 @@ def create_phenotype_features(row, phenotypes, phenotype_weight=5):

# Initialize and Train the SOM
positions, som = initialise_som(x_normalized)


# Get Winning Positions for Each SNP
positions = np.array([som.winner(x) for x in x_normalized])
Expand All @@ -123,7 +126,7 @@ def create_phenotype_features(row, phenotypes, phenotype_weight=5):
})

# Clustering SNPs Using K-Means
n_clusters = 7 # Adjust the number of clusters as needed
n_clusters = int(num_clusters)
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
positions_df = pd.DataFrame(positions, columns=['x', 'y'])
positions_df['cluster'] = kmeans.fit_predict(positions_df)
Expand Down Expand Up @@ -183,7 +186,7 @@ def create_phenotype_features(row, phenotypes, phenotype_weight=5):
title='SOM Clusters of SNPs with Detailed Hover Information',
xaxis=dict(title='SOM X', showgrid=False, zeroline=False),
yaxis=dict(title='SOM Y', showgrid=False, zeroline=False),
plot_bgcolor='black',
plot_bgcolor='rgba(0,0,0,0)',
height=800,
width=800,
legend=dict(
Expand Down Expand Up @@ -232,10 +235,43 @@ def initialise_som(x_normalized):
return positions, som


def clean_filters(filters):
if not filters:
return "All Categories"

# Split the filters by " OR "
filters_list = filters.split(" OR ")

# Remove the "category_string:==:" part from each filter
cleaned_filters = [f.split(":==:")[-1] for f in filters_list]

# Create a list to hold the formatted lines
formatted_lines = []

# Group the filters in chunks of 3
for i in range(0, len(cleaned_filters), 3):
# Get the chunk of filters
chunk = cleaned_filters[i:i + 3]
# Join them with a comma and a space
line = ", ".join(chunk)
# Add a comma at the end of the line if it's not the last chunk
if i + 3 < len(cleaned_filters):
line += ","
formatted_lines.append(line)

# Join the lines with a <br> tag to create the final formatted string
return "<br>".join(formatted_lines)


class SOMDiseaseView(APIView):
def get(self, request):
# Get the data_id from the request (passed as a query parameter)
data_id = request.GET.get('data_id')
# Get the number of clusters from the request (passed as a query parameter)
num_clusters = request.GET.get('num_clusters')
filters = request.GET.get('filters')
if num_clusters is None:
num_clusters = 5

# Retrieve the temporary CSV data object using the data_id
temp_data = get_object_or_404(TemporaryCSVData, id=data_id)
Expand Down Expand Up @@ -296,7 +332,7 @@ def create_allele_features(row, alleles, allele_weight=5):
})

# Clustering Diseases Using K-Means
n_clusters = 5
n_clusters = int(num_clusters)
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
positions_df = pd.DataFrame(positions, columns=['x', 'y'])
positions_df['cluster'] = kmeans.fit_predict(positions_df)
Expand Down Expand Up @@ -350,18 +386,47 @@ def create_allele_features(row, alleles, allele_weight=5):
hoverinfo='text'
))

# Decode and clean the filters
decoded_filters = urllib.parse.unquote(filters) if filters else "All Categories"
cleaned_filters = clean_filters(decoded_filters)

# Create the title for the visualization
title_text = (
f'SOM Clusters of Diseases with Detailed Hover Information<br>'
f'for {cleaned_filters} and {num_clusters} Clusters'
).title()

# Update the layout with the formatted title
fig.update_layout(
title='SOM Clusters of Diseases with Detailed Hover Information',
title=dict(
text=title_text,
x=0.5, # Center the title horizontally
y=.93, # Move the title higher up, closer to the top edge
xanchor='center',
yanchor='top',
font=dict(
size=16,
# Make the title bold
family='Arial, sans-serif',
color='black'
)
),
margin=dict(
# Calculate margin based on length of the title
t=100 + 20 * (len(cleaned_filters.split("<br>")) - 1),
),

xaxis=dict(title='SOM X', showgrid=False, zeroline=False),
yaxis=dict(title='SOM Y', showgrid=False, zeroline=False),
plot_bgcolor='black',
plot_bgcolor='rgba(0,0,0,0)',
height=800,
width=800,
legend=dict(
x=1.06,
y=0.7,
bgcolor='rgba(0,0,0,0)'
)
),
paper_bgcolor='rgba(0,0,0,0)'
)

fig.data[0].colorbar.update(
Expand All @@ -383,7 +448,9 @@ def create_allele_features(row, alleles, allele_weight=5):
'graph_div': graph_div,
'csv_path': f"{settings.MEDIA_URL}{file_name}",
"type": "disease",
"categories": categories
"categories": categories,
"filters": filters if filters is not None else categories,
"num_clusters": num_clusters
}

# Return the rendered HTML
Expand Down
5 changes: 5 additions & 0 deletions vis_phewas/static/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@
.panel h3,
.panel h4 {
text-align: center;
}

button{
text-shadow: 1px 0 3px black;
color: white; !important;
}
5 changes: 0 additions & 5 deletions vis_phewas/static/mainapp/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ body {
background-color: #97e4f6;
padding-top: 1%;
padding-bottom: 1%;
border-bottom: 1px solid #ccc;
margin: 0 auto;
}

Expand All @@ -205,10 +204,6 @@ body {
border-radius: 20px;
}

.button.exportQuery{
background-color: #08d0d9;
}

.button:disabled {
background-color: #ccc;
opacity: 0.25;
Expand Down
15 changes: 13 additions & 2 deletions vis_phewas/static/mainapp/js/som.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
function generateSOM(filters, type) {
function generateSOM(filters, type, num_clusters) {
// Validate the type parameter
if (type !== "allele" && type !== "disease") {
alert("Invalid SOM type specified. Must be 'allele' or 'disease'.");
return;
}
if (num_clusters == null) {
num_clusters = type === "disease" ? 5 : 7;
}

console.log("Generating SOM with filters: " + filters + ", type: " + type + ", num_clusters: " + num_clusters);

$.ajax({
url: "/api/send_data_to_som/",
type: "GET",
data: {
filters: filters, // Initialize with no filter but allow to pass filters
type: type,
num_clusters: num_clusters
},
success: function (response) {
// Determine the correct SOM visualization page based on the type
let url = type === "allele" ? "/som/SOMSNP/" : "/som/SOMDisease/";

// Open the SOM visualization page
if (filters === "") {
// If no filters, it means the SOM is generated for all data as an initial SOM
window.open(
url + "?data_id=" + response.data_id,
type === "allele" ? "AlleleSOMWindow" : "DiseaseSOMWindow",
"width=800,height=600,scrollbars=yes,resizable=yes"
);
} else {
window.open(url + "?data_id=" + response.data_id, "_self");
// If filters are specified, pass them as query parameters to the SOM page
window.open(url + "?data_id=" + encodeURIComponent(response.data_id) +
"&num_clusters=" + encodeURIComponent(response.num_clusters) +
"&filters=" + encodeURIComponent(filters), "_self");

}
},
error: function (xhr, status, error) {
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions vis_phewas/templates/mainapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</div>

</div>
<div class="toolbar-wrapper">{% include 'mainapp/toolbar.html' %}</div>
<div class="toolbar-wrapper">{% include 'mainapp/filterbar.html' %}</div>
</div>

<!-- Blank box for text and other content -->
Expand Down Expand Up @@ -72,7 +72,7 @@

<!-- Hidden content panels -->
<div id="options-panel" style="display: none">
{% include "mainapp/options.html" %}
{% include "mainapp/tools.html" %}
</div>

<div id="help-panel" style="display: none">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% load static %}
<script src={% static 'mainapp/js/som.js' %}></script>
<div class="panel options_panel">
<h3>Options</h3>
<h3>Tools</h3>
<h4>Resolution</h4>
<div class="resolutionSwitchContainer">
<span class="switch-label-left">2 digit</span>
Expand All @@ -18,8 +18,8 @@ <h4>Resolution</h4>

<div id="som_options" style="align-items: center; display: flex; flex-direction: column">
<h4>Generate SOM for Disease Clustering</h4>
<button class="button btn btn-info exportQuery" onclick="generateSOM('', 'disease')">Generate Disease SOM</button>
<button class="button btn btn-info exportQuery" onclick="generateSOM('', 'allele')">Generate Allele SOM</button>
<button class="button btn btn-info exportQuery" onclick="generateSOM('', 'disease', 5)" style="color: white">Generate Disease SOM</button>
<button class="button btn btn-info exportQuery" onclick="generateSOM('', 'allele', 7)" style="color: white">Generate Allele SOM</button>
</div>
</div>

Loading

0 comments on commit cc9b7c7

Please sign in to comment.