Skip to content

Commit

Permalink
Merge pull request #144 from CogStack/model-caching
Browse files Browse the repository at this point in the history
Model caching
  • Loading branch information
tomolopolis authored Aug 14, 2023
2 parents b3e9ba7 + c3c678f commit c94d7a6
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 99 deletions.
33 changes: 10 additions & 23 deletions webapp/api/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,24 +602,6 @@ def search_solr(request):
return search_collection(cdbs, query)


@api_view(http_method_names=['DELETE', 'POST'])
def cache_model(request, p_id):
method = request.method
project = ProjectAnnotateEntities.objects.get(id=p_id)
cat = get_cached_medcat(CAT_MAP=CAT_MAP, project=project)
if method == 'POST':
if cat is None:
get_medcat(CDB_MAP=CDB_MAP, VOCAB_MAP=VOCAB_MAP,
CAT_MAP=CAT_MAP, project=project)
return Response({'result': f'Successfully loaded model for project:{p_id}'})
else:
return Response({'result': f'Model already loaded for project:{p_id}'})
else:
clear_cached_medcat(CAT_MAP, project)
logger.info(f'Cleared cached model{p_id}')
return Response({'result': f'Cleared cached model:{p_id}'})


@api_view(http_method_names=['POST'])
def upload_deployment(request):
deployment_upload = request.data
Expand All @@ -628,15 +610,20 @@ def upload_deployment(request):
return Response("successfully uploaded", 200)


@api_view(http_method_names=['GET'])
def cache_model(_, cdb_id):
get_cached_cdb(cdb_id, CDB_MAP)
return Response("success", 200)
@api_view(http_method_names=['GET', 'DELETE'])
def cache_model(request, cdb_id):
if request.method == 'GET':
get_cached_cdb(cdb_id, CDB_MAP)
elif request.method == 'DELETE' and cdb_id in CDB_MAP:
del CDB_MAP[cdb_id]
else:
return Response(f'Invalid method or cdb_id:{cdb_id} is invalid / not loaded', 400)
return Response('success', 200)


@api_view(http_method_names=['GET'])
def model_loaded(_):
return Response({p.id: get_cached_medcat(CAT_MAP, p) is not None
return Response({p.id: False if not p.concept_db else p.concept_db.id in CDB_MAP
for p in ProjectAnnotateEntities.objects.all()})


Expand Down
2 changes: 1 addition & 1 deletion webapp/api/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
path('api/version/', api.views.version),
path('api/concept-db-search-index-created/', api.views.concept_search_index_available),
path('api/model-loaded/', api.views.model_loaded),
path('api/cache_model/<int:cdb_id>', api.views.cache_model),
path('api/cache-model/<int:cdb_id>/', api.views.cache_model),
path('api/upload-deployment/', api.views.upload_deployment),
path('api/model-concept-children/<int:cdb_id>/', api.views.cdb_cui_children),
path('api/metrics/', api.views.metrics),
Expand Down
24 changes: 17 additions & 7 deletions webapp/frontend/src/components/common/ClinicalText.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="note-container">
<loading-overlay :loading="loading">
<div slot="message">Preparing Document...</div>
<loading-overlay :loading="loading !== null">
<div slot="message">{{loading}}</div>
</loading-overlay>
<div v-if="!loading" class="clinical-note">
<v-runtime-template ref="clinicalText" :template="formattedText"></v-runtime-template>
Expand Down Expand Up @@ -34,10 +34,20 @@ export default {
taskValues: Array,
task: Object,
ents: Array,
loading: Boolean,
loading: String,
currentEnt: Object,
currentRelStartEnt: Object,
currentRelEndEnt: Object,
currentRelStartEnt: {
default () {
return {}
},
type: Object,
},
currentRelEndEnt: {
default () {
return {}
},
type: Object,
},
addAnnos: Boolean
},
data () {
Expand Down Expand Up @@ -73,9 +83,9 @@ export default {
styleClass = `highlight-task-${btnIndex}`
}
if ((this.ents[i] === this.currentRelStartEnt) || (this.ents[i].id === (this.currentRelStartEnt || {}).id)) {
if (this.ents[i] === this.currentRelStartEnt) {
styleClass += ' current-rel-start'
} else if ((this.ents[i] === this.currentRelEndEnt) || (this.ents[i].id === (this.currentRelEndEnt || {}).id)) {
} else if (this.ents[i] === this.currentRelEndEnt) {
styleClass += ' current-rel-end'
}
Expand Down
16 changes: 0 additions & 16 deletions webapp/frontend/src/components/common/ConceptSummary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -200,22 +200,6 @@ export default {
})
},
deep: true
},
'project': {
handler () {
let that = this
if (this.project.cdb_search_filter.length > 0) {
this.$http.get(`/api/concept-dbs/${this.project.cdb_search_filter[0]}/`).then(resp => {
if (resp.data) {
// this is a bit hacky - backend should just return the correct CDB search filter
that.$set(that, 'searchFilterDBIndex', `${resp.data.name}_id_${that.project.cdb_search_filter}`)
this.fetchDetail(this.selectedEnt, this.searchFilterDBIndex, () => {
that.cleanProps()
})
}
})
}
}
}
}
}
Expand Down
37 changes: 24 additions & 13 deletions webapp/frontend/src/views/Demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,17 @@
<textarea v-model="cuiFilters" class="form-control" name="cui"
rows="3" placeholder="Comma separated list: S-91175000, S-84757009"></textarea>
</div>
<div class="form-group">
<label>Type IDs Filter</label>
<textarea v-model="typeIDsFilters" class="form-control" name="type_ids"
rows="3" placeholder="Comma separated list: T-00010, T00020"></textarea>
</div>
<button @click="annotate()" class="btn btn-primary">Annotate</button>
</form>
</div>
<div class="view-port">
<div class="clinical-text">
<clinical-text :loading="loadingDoc" :text="annotatedText" :ents="ents"
<clinical-text :loading="loadingMsg" :text="annotatedText" :ents="ents"
:taskName="task" :taskValues="taskValues" @select:concept="selectEntity"></clinical-text>
</div>
<div class="sidebar">
<concept-summary :selectedEnt="currentEnt" :project="selectedProject"></concept-summary>
<concept-summary :selectedEnt="currentEnt" :project="selectedProject"
:searchFilterDBIndex="searchFilterDBIndex"></concept-summary>
</div>
</div>
</div>
Expand All @@ -57,13 +53,13 @@ export default {
projects: [],
selectedProject: {},
cuiFilters: '',
typeIDsFilters: '',
ents: [],
currentEnt: {},
annotatedText: '',
loadingDoc: false,
loadingMsg: null,
task: TASK_NAME,
taskValues: VALUES
taskValues: VALUES,
searchFilterDBIndex: null
}
},
created () {
Expand All @@ -89,11 +85,10 @@ export default {
project_id: this.selectedProject.id,
message: this.exampleText,
cuis: this.cuiFilters,
tuis: this.tuiFilters
}
this.loadingDoc = true
this.loadingMsg = 'Annotating Text...'
this.$http.post('/api/annotate-text/', payload).then(resp => {
this.loadingDoc = false
this.loadingMsg = null
this.ents = resp.data['entities'].map(e => {
e.assignedValues = {}
e.assignedValues[this.task] = this.taskValues[0]
Expand All @@ -105,6 +100,22 @@ export default {
},
selectEntity (entIndex) {
this.currentEnt = this.ents[entIndex]
},
fetchCDBSearchIndex () {
if (this.selectedProject.cdb_search_filter.length > 0) {
this.$http.get(`/api/concept-dbs/${this.selectedProject.cdb_search_filter[0]}/`).then(resp => {
if (resp.data) {
this.searchFilterDBIndex = `${resp.data.name}_id_${this.selectedProject.cdb_search_filter}`
}
})
}
}
},
watch: {
'selectedProject': {
handler () {
this.fetchCDBSearchIndex()
}
}
}
}
Expand Down
31 changes: 18 additions & 13 deletions webapp/frontend/src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@
<template #cell(model_loaded)="data">
<div v-if="cdbLoaded[data.item.id]">
<button class="btn btn-outline-success model-up">
<font-awesome-icon icon="times" class="clear-model-cache" @click="clearLoadedModel(data.item.id)"></font-awesome-icon>
<font-awesome-icon icon="times" class="clear-model-cache" @click="clearLoadedModel(data.item.concept_db)"></font-awesome-icon>
<font-awesome-icon icon="fa-cloud-arrow-up"></font-awesome-icon>
</button>
</div>
<div v-if="!cdbLoaded[data.item.id]">
<button class="btn btn-outline-secondary" @click="loadProjectCDB(data.item.id)">
<font-awesome-icon v-if="loadingModel !== data.item.id" icon="fa-cloud-arrow-up"></font-awesome-icon>
<font-awesome-icon v-if="loadingModel === data.item.id" icon="spinner" spin></font-awesome-icon>
<button class="btn btn-outline-secondary" @click="loadProjectCDB(data.item.concept_db)">
<font-awesome-icon v-if="loadingModel !== data.item.concept_db" icon="fa-cloud-arrow-up"></font-awesome-icon>
<font-awesome-icon v-if="loadingModel === data.item.concept_db" icon="spinner" spin></font-awesome-icon>
</button>
</div>
</template>
Expand All @@ -125,13 +125,18 @@
<transition name="alert"><div class="alert alert-danger" v-if="modelCacheLoadError" role="alert">Error loading MedCAT model for project</div></transition>
<transition name="alert"><div class="alert alert-danger" v-if="projectLockedWarning" role="alert">Unable load a locked project. Contact your CogStack administrator to unlock</div></transition>
</div>

<modal v-if="clearModelModal" :closable="true" @modal:close="clearModelModal = false">
<div slot="header">
<h3>Confirm Clear Cached Model State</h3>
</div>
<div slot="body">
Confirm clearing cached MedCAT Model for Project {{clearModelModal}} (any other Projects that use this model). This will remove any interim training
done (if any). To recover the cached model, re-open the project(s), and re-submit all documents. If you're unsure you should not clear the model state.
<p>Confirm clearing cached MedCAT Model for Concept DB {{clearModelModal}} (and any other Projects that use this model). </p>
<p>
This will remove any interim training done (if any).
To recover the cached model, re-open the project(s), and re-submit all documents.
If you're unsure you should not clear the model state.
</p>
</div>
<div slot="footer">
<button class="btn btn-primary" @click="confirmClearLoadedModel(clearModelModal)">Confirm</button>
Expand Down Expand Up @@ -267,18 +272,18 @@ export default {
this.cdbLoaded = resp.data
})
},
clearLoadedModel (projId) {
this.clearModelModal = projId
clearLoadedModel (cdbId) {
this.clearModelModal = cdbId
},
confirmClearLoadedModel (projId) {
confirmClearLoadedModel (cdbId) {
this.clearModelModal = false
this.$http.delete(`/api/cache-model/${projId}/`).then(_ => {
this.$http.delete(`/api/cache-model/${cdbId}/`).then(_ => {
this.fetchCDBsLoaded()
})
},
loadProjectCDB (projId) {
this.loadingModel = projId
this.$http.post(`api/cache-model/${projId}/`).then(_ => {
loadProjectCDB (cdbId) {
this.loadingModel = cdbId
this.$http.get(`/api/cache-model/${cdbId}/`).then(_ => {
this.loadingModel = false
this.fetchCDBsLoaded()
}).catch(_ => {
Expand Down
59 changes: 33 additions & 26 deletions webapp/frontend/src/views/TrainAnnotations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
<div class="app-main">
<document-summary :docs="docs" :moreDocs="nextDocSetUrl !== null"
:validatedDocIds="validatedDocuments"
:selectedDocId="currentDoc !== null ? currentDoc.id : null" :loadingDoc="loadingDoc"
:selectedDocId="currentDoc !== null ? currentDoc.id : null" :loadingDoc="loadingMsg !== null"
@request:nextDocSet="fetchDocuments()" @request:loadDoc="loadDoc"></document-summary>
<div class="main-viewport">
<clinical-text :loading="loadingDoc" :text="currentDoc !== null ? currentDoc.text : null"
<clinical-text :loading="loadingMsg" :text="currentDoc !== null ? currentDoc.text : null"
:current-ent="currentEnt" :ents="ents" :task-name="taskName" :task-values="taskValues"
:addAnnos="true" :current-rel-start-ent="(currentRel || {}).start_entity"
:current-rel-end-ent="(currentRel || {}).end_entity"
Expand Down Expand Up @@ -319,7 +319,7 @@ export default {
currentDoc: null,
currentEnt: null,
currentRel: null,
loadingDoc: false,
loadingMsg: null,
resubmittingAllDocs: false,
resubmitSuccess: false,
helpModal: false,
Expand Down Expand Up @@ -425,25 +425,32 @@ export default {
this.prepareDoc()
},
prepareDoc () {
this.loadingDoc = true
let payload = {
project_id: this.project.id,
document_ids: [this.currentDoc.id]
}
if (this.validatedDocuments.indexOf(this.currentDoc.id) === -1) {
payload['update'] = 1
}
this.$http.post('/api/prepare-documents/', payload).then(_ => {
// assuming a 200 is fine here.
this.fetchEntities()
}).catch(err => {
this.errors.modal = true
if (err.response) {
this.errors.message = err.response.data.message || 'Internal Server Error.'
this.errors.description = err.response.data.description || ''
this.errors.stacktrace = err.response.data.stacktrace
this.loadingMsg = "Loading MedCAT model..."
this.$http.get(`/api/cache-model/${this.project.concept_db}/`).then(_ => {
this.loadingMsg = "Preparing Document..."
let payload = {
project_id: this.project.id,
document_ids: [this.currentDoc.id]
}
if (this.validatedDocuments.indexOf(this.currentDoc.id) === -1) {
payload['update'] = 1
}
this.$http.post('/api/prepare-documents/', payload).then(_ => {
// assuming a 200 is fine here.
this.fetchEntities()
}).catch(err => {
this.errors.modal = true
if (err.response) {
this.errors.message = err.response.data.message || 'Internal Server Error.'
this.errors.description = err.response.data.description || ''
this.errors.stacktrace = err.response.data.stacktrace
}
})
}).catch(_ => {
this.errors.modal = true
this.errors.mesasge = "Internal server error - cannot load MedCAT model. Contact your MedCAT admin quoting this project ID"
})
},
fetchEntities (selectedEntId) {
let params = this.nextEntSetUrl === null ? `?project=${this.projectId}&document=${this.currentDoc.id}`
Expand Down Expand Up @@ -484,7 +491,7 @@ export default {
: this.ents[0]
this.metaAnnotate = this.currentEnt && (this.currentEnt.assignedValues[TASK_NAME] === CONCEPT_ALTERNATIVE ||
this.currentEnt.assignedValues[TASK_NAME] === CONCEPT_CORRECT)
this.loadingDoc = false
this.loadingMsg = null
if (this.$route.query.annoStart && this.$route.query.annoEnd) {
const ent = _.find(this.ents, e => {
return Number(this.$route.query.annoStart) === e.start_ind &
Expand Down Expand Up @@ -709,18 +716,18 @@ export default {
}
subPromises.push(this.$http.post(`/api/submit-document/`, payload))
this.resubmittingAllDocs = true
this.loadingDoc = true
this.loadingMsg = "Submitting document annotations..."
Promise.all(subPromises).then(_ => {
this.resubmitSuccess = true
this.loadingDoc = false
this.loadingMsg = null
this.resubmittingAllDocs = false
const that = this
setTimeout(function () {
that.resubmitSuccess = false
}, 10000)
}).catch(() => {
this.resubmittingAllDocs = true
this.loadingDoc = false
this.loadingMsg = null
this.errors.modal = true
this.errors.message = 'Failure re-submitting all validated documents: Refresh Project'
})
Expand All @@ -740,7 +747,7 @@ export default {
window.addEventListener('keydown', this.keydown)
},
confirmReset () {
this.loadingDoc = true
this.loadingMsg = "Resetting document annotations..."
const payload = {
project_id: this.project.id,
document_ids: [this.currentDoc.id],
Expand All @@ -749,7 +756,7 @@ export default {
this.$http.post('/api/prepare-documents/', payload).then(_ => {
this.fetchEntities()
this.resetModal = false
this.loadingDoc = false
this.loadingMsg = null
}).catch(err => {
this.resetModal = false
this.errors.modal = true
Expand Down

0 comments on commit c94d7a6

Please sign in to comment.