Skip to content

Commit

Permalink
new nuclei viewer page! (#358)
Browse files Browse the repository at this point in the history
* new nuclei viewer page!

* removing print statements/comments
  • Loading branch information
vrose99 authored Nov 3, 2022
1 parent 37aaa60 commit 7484f95
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 5 deletions.
2 changes: 2 additions & 0 deletions neuvue_project/neuvue/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@
NEUVUE_CLIENT_SETTINGS = {
# "local" : True
}
NUCLEUS_NUERON_SVM = 'nucleus_neuron_svm'
CELL_CLASS_MODEL = 'allen_soma_coarse_cell_class_model_v2'
# Task Timeout in Seconds
TIMEOUT = 900

Expand Down
3 changes: 3 additions & 0 deletions neuvue_project/neuvue/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
LineageView,
TokenView,
SynapseView,
NucleiView,
GettingStartedView,
SaveStateView,
SaveOperationsView
Expand Down Expand Up @@ -56,6 +57,8 @@
path('synapse/', SynapseView.as_view(), name="synapse"),
path('synapse/<str:root_ids>', SynapseView.as_view(), name="synapse"),
path('synapse/<str:root_ids>/<str:pre_synapses>/<str:post_synapses>/<str:cleft_layer>/<str:timestamp>', SynapseView.as_view(), name="synapse"),
path('nuclei/', NucleiView.as_view(), name="nuclei"),
path('nuclei/<str:nuclei_ids>', NucleiView.as_view(), name="nuclei"),
path('report/', ReportView.as_view(), name="report"),
path('userNamespace/', UserNamespaceView.as_view(), name="user-namespace"),
path('save_state', SaveStateView.as_view(), name="save-state"),
Expand Down
11 changes: 9 additions & 2 deletions neuvue_project/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,15 @@
<li class="nav-item">
<a class="nav-link" href="{% url "inspect" %}">Inspect Task</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "synapse" %}">Synapse Viewer</a>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Neuron Viewers
</a>
<ul class="dropdown-menu me-2 dropdown-menu-dark preferences" aria-labelledby="navbarDropdown">
<li class="nav-item"> <a class="nav-link" href="{% url "synapse" %}">Synapse Viewer</a> </li>
<li><hr class="dropdown-divider"></li>
<li class="nav-item"> <a class="nav-link" href="{% url "nuclei" %}">Nuclei Viewer</a> </li>
</ul>
</li>

<li class="nav-item">
Expand Down
127 changes: 127 additions & 0 deletions neuvue_project/templates/nuclei.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{% extends "base.html" %}
{% load static %}
{% load socialaccount %}

{% block content %}

{% if not nuclei_ids or error %}

<div class="basic workspace">
<div class="inspect-container">
<form id="mainForm" action="" method="post" onSubmit="triggerLoadingSpinner('submit-spinner')">
{% csrf_token %}
<div class="form-group">
<label class="text-white-50" for="rootIDInput">Nuclei ID (Enter Nuclei ID (s) separated by commas)</label>
<input class="form-control" id="rootIDInput" name="nuclei_ids" required="true">
</div>
<br>
<div class="d-flex">
<input type="submit" class="btn btn-primary" value="Submit">
<div id="submit-spinner" class="text-white mx-3 mt-2"></div>
{% if error%}
<small id="errorMessage" class="form-text text-danger"> {{error}} </small>
{% endif %}
</div>
</form>
</div>
</div>

{% else %}

<div id="neuVue-sidemenu" class="sidemenu">
<div id="neuVue-sidebar" class="sidebar">
<a id = "sidebarActivate" class="fill-div" onclick="sidemenu_content()">
<i class="glyphicon glyphicon-list"></i>
</a>
</div>
<div id="neuVue-sidecontent" class="sidecontent">

<! Task Information >
<div id = "instruction-container" class ="sideContentBox" style="max-height:70%;">
<div class="sideContentTitle">
Nuclei Information
</div>
<div class="sideContentInfo">
<table class="table table-dark table-bordered table-hover">
{{cell_types|safe}}
</table>
</div>
</div>
<div id = "instruction-container" class ="sideContentBox" style="padding: 0; border:transparent !important;">
<div class="sideContentInfo">
<button type="button" class="btn btn-info" onclick="getLink()"> <i class="fa fa-copy"></i> Copy Link to Clipboard </button> </br>
<button type="button" class="btn btn-info" id="exportBtn_nucleiIDs"> <i class="fa fa-download"></i> Export Nuclei Info </button></br>
</div>
</div>

</div>
</div>

<div id="neuroglancer" class="leftFormatting">
<div class="left">
<div id="neuroglancer-container" class="neuroglancer-container"></div>
<div class="bottomBar">
<button id="btnExit" type="button" class="mainButton" onclick= "document.location='{% url 'nuclei' %}'">
Exit
</button>
</div>
</div>
</div>
<script type="text/javascript" src="{% static "js/utils.js" %}"></script>
<script type="text/javascript" src="{% static "workspace/main.bundle.js" %}"></script>
{% endif%}

<div id="hiddenTable" style="visibility: hidden;">
</div>
<style> .overlay-hidden { display:none; } </style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script type="text/javascript" src="{% static "js/utils.js" %}"></script>
<script src="https://cdn.jsdelivr.net/npm/table2csv@1.1.3/src/table2csv.js" integrity="sha384-vVCd7tQ0g9opUDOT/X+Dsb5u1xXL/2bhtBkeV4TWA0/x5lDgMNyl/YqrEFMMPUZL" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.js"></script>
<script type="text/javascript">

const exportButton_userTable = document.getElementById("exportBtn_nucleiIDs");
exportButton_userTable.addEventListener('click', (e) => {
let tableContents = `
{{cell_types|safe}}`
$('#hiddenTable').html(`
<table id='hiddenIDTable' class="table table-striped table-hover">${tableContents}</table>
`);
// Construct filename
const filename = 'Nuclei_IDs' + '_' + + Date.now() + '.csv';
// Perform download
$('#hiddenIDTable').table2csv('download', {'filename': filename});
});

// Neuroglancer State Load

function getLink() {
viewer.postJsonState(true, undefined, true, function() {
let url_prefix = "https://neuroglancer.neuvue.io/?json_url="
copyToClipboard(url_prefix.concat(viewer.saver.savedUrl));
});
}

function get_nuclei_info() {
const nuclei_info = `{{cell_types|safe}}`;
return nuclei_info;
}

$(document).ready(function() {
{% if ng_state %}
const state = {{ ng_state|safe }};
openSideMenu();
viewer.state.restoreState(state);
{% endif %}
})

// Remove loading spinner when page loads. This is important if the back button is clicked
window.addEventListener('pageshow', function(e) {
if (document.getElementById('submit-spinner') !== null) {
removeLoadingSpinner('submit-spinner');
}
})

</script>
{% endblock %}
79 changes: 76 additions & 3 deletions neuvue_project/workspace/neuroglancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def create_path_state():

def create_point_state(name='annotations', group=None, description=None, color=None):
"""Create the annotation state for points.
Dontt tuse linemapper, just creates a neuroglancer link that is just Points
Dont use linemapper, just creates a neuroglancer link that is just Points
nglui statebuilder
Returns:
StateBuilder: Annotation State
Expand Down Expand Up @@ -323,11 +323,11 @@ def _get_soma_center(root_ids: List, cave_client):
array: array for the position of the soma
"""
try:
soma_df = cave_client.materialize.query_table('nucleus_neuron_svm', filter_in_dict={
soma_df = cave_client.materialize.query_table(settings.NUCLEUS_NUERON_SVM, filter_in_dict={
'pt_root_id': root_ids[:3]
})
if not len(soma_df):
soma_df = cave_client.materialize.query_table('nucleus_neuron_svm', filter_in_dict={
soma_df = cave_client.materialize.query_table(settings.NUCLEUS_NUERON_SVM, filter_in_dict={
'pt_root_id': root_ids
})
if len(soma_df) > 3:
Expand Down Expand Up @@ -672,6 +672,79 @@ def construct_synapse_state(root_ids: List, flags: dict = None):
})
return json.dumps(state_dict), synapse_stats

def construct_nuclei_state(nuclei_ids: List):
"""Construct state for the synapse viewer.
Args:
root_ids (list): segment root id
flags (dict): query parameters
- pre_synapses
- post_synapses
- cleft_layer
- timestamp
Returns:
string: json-formatted state
dict: synapse stats
"""
cave_client = CAVEclient('minnie65_phase3_v1', auth_token=os.environ['CAVECLIENT_TOKEN'])
soma_df = cave_client.materialize.query_table(settings.NUCLEUS_NUERON_SVM, filter_in_dict={
'id': nuclei_ids
})

root_ids = soma_df['pt_root_id'].values
nuclei_points = np.array(soma_df['pt_position'].values)
position = nuclei_points[0] if len(nuclei_points) else [] # check what happens when bad values are returned -- add an error case

data_list = [None]
base_state = create_base_state(root_ids, position)

# Random color generation
r = lambda: random.randint(0, 255)
states = [base_state]

def generate_cell_type_table(soma_df):
"""Generates Cell type table
returns filtered list of valid ids (i.e. listed in NUCLEUS_NEURON_SVM table) and their cell types if available, else NaN
"""
def get_cell_type(nuclei_id, cell_class_df):
filtered_row = cell_class_df[cell_class_df.id == nuclei_id]
cell_type = filtered_row.cell_type.values[0] if len(filtered_row) else "NaN"
return cell_type

cell_class_info_df = cave_client.materialize.query_table(settings.CELL_CLASS_MODEL, filter_in_dict={
'id': soma_df.id.values
})

updated_soma_df = pd.merge(soma_df, cell_class_info_df, on='id', how='outer')
updated_soma_df.cell_type_y = updated_soma_df.cell_type_y.fillna('unknown')

type_table = '<thead><tr><th>Nuclei ID</th><th>Type</th></tr></thead><tbody>'
for nucleus_id in updated_soma_df.id.values:
cell_type = get_cell_type(nucleus_id, cell_class_info_df)
type_table += '<tr><td>'+str(nucleus_id)+'</td><td>'+cell_type+'</td><tr>'
type_table += '</tbody>'

return type_table, updated_soma_df


cell_type_table, soma_df = generate_cell_type_table(soma_df)

for cell_type, type_df in soma_df.groupby('cell_type_y'):
data_list.append(generate_point_df(np.array(type_df['pt_position_x'].values)))
states.append(
create_point_state(name=f'{cell_type}_nuclei_points', color='#{:02x}{:02x}{:02x}'.format(r(), r(), r())))

chained_state = ChainedStateBuilder(states)

state_dict = chained_state.render_state(return_as='dict', data_list=data_list)
state_dict['layout'] = '3d'
state_dict["selectedLayer"] = {"layer": "seg", "visible": True}
state_dict['jsonStateServer'] = settings.JSON_STATE_SERVER

return json.dumps(state_dict), cell_type_table


def refresh_ids(ng_state:str, namespace:str):
namespace = Namespace.objects.get(namespace=namespace)
if not namespace.refresh_selected_root_ids:
Expand Down
37 changes: 37 additions & 0 deletions neuvue_project/workspace/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
construct_proofreading_state,
construct_lineage_state_and_graph,
construct_synapse_state,
construct_nuclei_state,
get_from_state_server,
post_to_state_server,
get_from_json,
Expand Down Expand Up @@ -820,6 +821,42 @@ def post(self, request, *args, **kwargs):
"timestamp": timestamp
}))

class NucleiView(View):
def get(self, request, nuclei_ids=None, *args, **kwargs):
if not is_authorized(request.user):
logging.warning(f'Unauthorized requests from {request.user}.')
return redirect(reverse('index'))

if nuclei_ids in settings.STATIC_NG_FILES:
return redirect(f'/static/workspace/{nuclei_ids}', content_type='application/javascript')

context = {
"nuclei_ids": None,
"error": None
}

if nuclei_ids is None:
return render(request, "nuclei.html", context)

nuclei_ids = [x.strip() for x in nuclei_ids.split(',')]

try:
context['nuclei_ids'] = nuclei_ids
context['ng_state'], context['cell_types'] = construct_nuclei_state(nuclei_ids=nuclei_ids)
except Exception as e:
context['error'] = e

return render(request, "nuclei.html", context)


def post(self, request, *args, **kwargs):
nuclei_ids = request.POST.get("nuclei_ids")

return redirect(reverse('nuclei', kwargs={
"nuclei_ids": nuclei_ids,
}))



#TODO: Move simple views to other file
class IndexView(View):
Expand Down

0 comments on commit 7484f95

Please sign in to comment.