From 3f4bad92816ece3f153086d8fb374f0492a3ee21 Mon Sep 17 00:00:00 2001 From: Dan Levitas Date: Thu, 17 Aug 2023 16:04:01 +0000 Subject: [PATCH] [RFR] change some underlying code on Series/Objects pages, and clean up --- handler/ezBIDS_core/createThumbnailsMovies.py | 53 -------- handler/ezBIDS_core/ezBIDS_core.py | 18 +-- handler/ezBIDS_core/update_ezBIDS_core.py | 4 +- ui/src/Events.vue | 26 ++-- ui/src/Objects.vue | 128 +++++++++--------- ui/src/SeriesPage.vue | 71 +++++++--- ui/src/libUnsafe.ts | 23 +--- ui/src/store/index.ts | 58 ++++---- 8 files changed, 174 insertions(+), 207 deletions(-) mode change 100644 => 100755 ui/src/SeriesPage.vue diff --git a/handler/ezBIDS_core/createThumbnailsMovies.py b/handler/ezBIDS_core/createThumbnailsMovies.py index 3740fe6b..23f8c896 100755 --- a/handler/ezBIDS_core/createThumbnailsMovies.py +++ b/handler/ezBIDS_core/createThumbnailsMovies.py @@ -27,59 +27,6 @@ # Functions -# def create_movie_thumbnails(nifti_file, output_dir, object_img_array, v): -# """ -# Generates a PNG for each volume of a 4D acquisition. - -# Parameters -# ---------- - -# nifti_file : string -# path of 4D nifti file. - -# output_dir: string -# path of folder where movie PNG files will be stored - -# object_img_array: numpy.darray -# result of nib.load(nifti_file).dataobj - -# v: int -# volume index -# """ - -# max_len = len(str(object_img_array.shape[3])) - -# if not os.path.isfile("{}/{}.png".format(output_dir, v)): - -# slice_x = object_img_array[floor(object_img_array.shape[0] / 2), :, :, v] -# slice_y = object_img_array[:, floor(object_img_array.shape[1] / 2), :, v] -# slice_z = object_img_array[:, :, floor(object_img_array.shape[2] / 2), v] - -# fig, axes = plt.subplots(1, 3, figsize=(9, 3)) -# for index, slices in enumerate([slice_x, slice_y, slice_z]): -# axes[index].imshow(slices.T, cmap="gray", origin="lower", aspect="auto") -# axes[index].axis("off") -# plt.tight_layout(pad=0, w_pad=0, h_pad=0) -# plt.close() - -# fig.canvas.draw() - -# w, h = fig.canvas.get_width_height() -# buf = np.frombuffer(fig.canvas.tostring_argb(), dtype=np.uint8) -# buf.shape = (w, h, 4) - -# buf = np.roll(buf, 3, axis=2) - -# w, h, d = buf.shape -# png = Image.frombytes("RGBA", (w, h), buf.tobytes()) - -# # Sort files in UNIX-friendly way (i.e. zero pad) -# v_len = len(str(v)) -# new_v = "0" * (max_len - v_len) + str(v) - -# png.save("{}/{}.png".format(output_dir, new_v)) - - def create_thumbnail(nifti_file, image): """ Generates a PNG for the 2nd volume of a 4D acquisition. diff --git a/handler/ezBIDS_core/ezBIDS_core.py b/handler/ezBIDS_core/ezBIDS_core.py index 62f6e456..75e07607 100755 --- a/handler/ezBIDS_core/ezBIDS_core.py +++ b/handler/ezBIDS_core/ezBIDS_core.py @@ -1987,13 +1987,15 @@ def entity_labels_identification(dataset_list_unique_series, lookup_dic): # echo if (unique_dic["EchoNumber"] - and not any(x in unique_dic["type"] for x in ["fmap/epi", - "fmap/magnitude1", - "fmap/magnitude2", - "fmap/phasediff", - "fmap/phase1", - "fmap/phase2", - "fmap/fieldmap"])): + and not any(x in unique_dic["type"]for x in + [ + "fmap/epi", + "fmap/magnitude1", + "fmap/magnitude2", + "fmap/phasediff", + "fmap/phase1", + "fmap/phase2", + "fmap/fieldmap"])): series_entities["echo"] = str(unique_dic["EchoNumber"]) # flip @@ -2232,7 +2234,6 @@ def modify_objects_info(dataset_list): items.append({"path": item, "name": "nii.gz", "pngPaths": [], - "moviePath": None, "headers": protocol["headers"]}) # Objects-level info for ezBIDS_core.json @@ -2297,6 +2298,7 @@ def extract_series_info(dataset_list_unique_series): "nifti_path": unique_dic["nifti_path"], "series_idx": unique_dic["series_idx"], "AcquisitionDateTime": unique_dic["AcquisitionDateTime"], + "PED": unique_dic["direction"], "entities": unique_dic["entities"], "type": unique_dic["type"], "error": unique_dic["error"], diff --git a/handler/ezBIDS_core/update_ezBIDS_core.py b/handler/ezBIDS_core/update_ezBIDS_core.py index 34c90879..76e82265 100755 --- a/handler/ezBIDS_core/update_ezBIDS_core.py +++ b/handler/ezBIDS_core/update_ezBIDS_core.py @@ -3,7 +3,7 @@ """ Created on Tue Jan 25 13:55:10 2022 -update ezBIDS_core.json with pngPaths and moviePath +update ezBIDS_core.json with pngPaths @author: dlevitas """ @@ -20,7 +20,7 @@ json_list = pd.read_csv("list", header=None, lineterminator="\n").to_numpy().flatten().tolist() -# place paths to thumbnails and movies in ezBIDS_core.json +# place paths to image thumbnails in ezBIDS_core.json with open("ezBIDS_core.json", "r") as ezBIDS_json: ezBIDS = json.load(ezBIDS_json) diff --git a/ui/src/Events.vue b/ui/src/Events.vue index e4824d0f..85228069 100755 --- a/ui/src/Events.vue +++ b/ui/src/Events.vue @@ -341,19 +341,19 @@ export default defineComponent({ //create new event objects const eventObjects = createEventObjects(this.ezbids, files); - //adjust eventObjects error message based on exclusion of corresponding func/bold - this.ezbids.objects.forEach((o:IObject)=>{ - let correspondingFuncBoldEvents = eventObjects.filter(object=>object.ModifiedSeriesNumber == o.ModifiedSeriesNumber && o._type == "func/bold") - if(correspondingFuncBoldEvents.length > 0) { - correspondingFuncBoldEvents.forEach(object=>{ - object.analysisResults.errors = [] - - if(o._exclude == true) { - object.analysisResults.errors = [`The corresponding func/bold (#${o.series_idx}) to this acquisition has been set (or was set) to exclude from BIDS conversion. Recommendation is to also exclude this acquisition from BIDS conversion (assuming the func/bold is still set for exclusion), unless you have good reason for keeping it.`] - } - }) - } - }) + // //adjust eventObjects error message based on exclusion of corresponding func/bold + // this.ezbids.objects.forEach((o:IObject)=>{ + // let correspondingFuncBoldEvents = eventObjects.filter(object=>object.ModifiedSeriesNumber == o.ModifiedSeriesNumber && o._type == "func/bold") + // if(correspondingFuncBoldEvents.length) { + // correspondingFuncBoldEvents.forEach(object=>{ + // object.analysisResults.errors = [] + + // if(o._exclude == true) { + // object.analysisResults.errors = [`The corresponding func/bold (#${o.series_idx}) to this acquisition has been set (or was set) to exclude from BIDS conversion. Recommendation is to also exclude this acquisition from BIDS conversion (assuming the func/bold is still set for exclusion), unless you have good reason for keeping it.`] + // } + // }) + // } + // }) eventObjects.forEach(object=>{ this.$store.commit("addObject", object); diff --git a/ui/src/Objects.vue b/ui/src/Objects.vue index 33af66b2..a33025ce 100755 --- a/ui/src/Objects.vue +++ b/ui/src/Objects.vue @@ -31,7 +31,7 @@ @click="select(o, o_ses)"> #{{o.series_idx}}  - +  ({{o._SeriesDescription}}) @@ -473,86 +473,82 @@ break; } } - - //for func/sbref object(s), update validationWarnings if corresponding func/bold has been excluded + + /* Ensure direction (dir) entity labels are capitalized (e.g. AP, not ap). + Can occur when user adds this themselves. + */ + if(o._entities.direction && o._entities.direction !== "") { + if(o._entities.direction !== o._entities.direction.toUpperCase()) { + o.validationErrors.push("Please ensure that the phase-encoding direction entity label is fully capitalized") + } + } + + //func/sbref are implicitly linked to a func/bold; make sure these have same entities and exclusion criteria if(o._type == "func/sbref") { - let funcBoldObjects = this.$store.state.ezbids.objects.filter(o=>o._type == "func/bold" && (o._entities.part == "" || o._entities.part == "mag" || o._entities.part == "phase")) - funcBoldObjects.forEach(func=>{ - - //TODO - rewrite this. - let funcEntities = Object.fromEntries(Object.entries(func._entities).filter(([_, v]) => v != "")); //remove empty entity labels - let objEntities = Object.fromEntries(Object.entries(o._entities).filter(([_, v]) => v != "")); //remove empty entity labels - if(deepEqual(funcEntities, objEntities)) { - o.ModifiedSeriesNumber = func.ModifiedSeriesNumber - o.analysisResults.section_id = func.analysisResults.section_id - - o.validationWarnings = []; - - if(func._exclude === true || func._type == "exclude") { - o.exclude = true - o._exclude = true - o.validationWarnings = [`The corresponding func/bold #"+func.series_idx+" is currently set to exclude from BIDS conversion. \ - Since this func/sbref is linked, it will also be excluded from conversion. Please modify if incorrect.`] + let correspondingFuncBold = this.ezbids.objects.filter((object:IObject)=>parseInt(object.ModifiedSeriesNumber) == parseInt(o.ModifiedSeriesNumber) + 1 && object._type == "func/bold") //func/sbref [should] always come right before their func/bold + if(correspondingFuncBold) { // should be no more than one + correspondingFuncBold.forEach((boldObj:IObject)=>{ + o.analysisResults.section_id = boldObj.analysisResults.section_id + for(let k in boldObj._entities) { + if(boldObj._entities[k] !== "" && k !== "echo") { + if(k === "part" && boldObj._entities[k] === "phase") { + //pass + } else { + o._entities[k] = boldObj._entities[k] + } + } } - if(func._exclude === false) { + if(boldObj._exclude === true || correspondingFuncBold._type === "exclude") { + o.exclude = true + o._exclude = true + o.validationWarnings = [`The corresponding func/bold #${boldObj.series_idx} is currently set to exclude from BIDS conversion. \ + Since this func/sbref is linked, it will also be excluded from conversion unless the corresponding + func/bold is unexcluded. Please modify if incorrect.`] + } + if(boldObj._exclude === false) { o.exclude = false o._exclude = false o.validationWarnings = [] } - } - }) + }) + } } - //for func/events object(s), update series_idx and ModifiedSeriesNumber to match corresponding func/bold object. - //Also update validationWarnings if corresponding func/bold has been excluded + //func/events are implicitly linked to a func/bold; make sure these have same entities and exclusion criteria if(o._type == "func/events") { - let funcBoldObjects = this.$store.state.ezbids.objects.filter(o=>o._type == "func/bold" && (o._entities.part == "" || o._entities.part == "mag" || o._entities.part == "phase")) - funcBoldObjects.forEach(func=>{ - - //TODO - rewrite this. - let funcEntities = Object.fromEntries(Object.entries(func._entities).filter(([_, v]) => v != "")); //remove empty entity labels - let objEntities = Object.fromEntries(Object.entries(o._entities).filter(([_, v]) => v != "")); //remove empty entity labels - if(deepEqual(funcEntities, objEntities)) { - o.ModifiedSeriesNumber = func.ModifiedSeriesNumber - o.analysisResults.section_id = func.analysisResults.section_id - - o.validationWarnings = []; - - if(func._exclude === true || func._type == "exclude") { - o.exclude = true - o._exclude = true - o.validationWarnings = ["The corresponding func/bold #"+func.series_idx+" is currently set to exclude from BIDS conversion. \ - Since this func/sbref is linked, it will also be excluded from conversion. Please modify if incorrect."] + let correspondingFuncBold = this.ezbids.objects.filter((object:IObject)=>object._type == "func/bold" && + object._entities.subject == o._entities.subject && + object._entities.session == o._entities.session && + object._entities.task == o._entities.task && + object._entities.run == o._entities.run) + if(correspondingFuncBold) { // should be no more than one + correspondingFuncBold.forEach((boldObj:IObject)=>{ + o.ModifiedSeriesNumber = boldObj.ModifiedSeriesNumber + o.analysisResults.section_id = boldObj.analysisResults.section_id + for(let k in boldObj._entities) { + if(boldObj._entities[k] !== "" && k !== "echo") { + if(k === "part" && boldObj._entities[k] === "phase") { + //pass + } else { + o._entities[k] = boldObj._entities[k] + } + } } - if(func._exclude === false) { + if(boldObj._exclude === true || correspondingFuncBold._type === "exclude") { + o.exclude = true + o._exclude = true + o.validationWarnings = [`The corresponding func/bold #${boldObj.series_idx} is currently set to exclude from BIDS conversion. \ + Since this func/events is linked, it will also be excluded from conversion unless the corresponding + func/bold is unexcluded. Please modify if incorrect.`] + } + if(boldObj._exclude === false) { o.exclude = false o._exclude = false o.validationWarnings = [] } - } - }) - - //find bold object with the same set of entities - const matchingBold = this.$store.state.ezbids.objects - .filter((o:IObject)=>o._type == "func/bold") - ////common func/bold acquisitions are magnitude ("mag"); the part entity label can have this value or be left blank. Both convey the same information - .filter((o:IObject)=>(o._entities.part == "" || o._entities.part == "mag")) - .find((func:IObject)=>{ - - for(let k in o._entities) { - if(o._entities[k] != func._entities[k]) return false; - } - return true - }); - - if(matchingBold) { - o.ModifiedSeriesNumber = matchingBold.ModifiedSeriesNumber; - o.analysisResults.section_id = matchingBold.analysisResults.section_id; - if(matchingBold._exclude) { - o.validationWarnings = ["The corresponding func/bold #${matchingBold.series_idx} is currently set to exclude from BIDS conversion. \ - Since this func/sbref is linked, it will also be excluded from conversion. Please modify if incorrect."] - } + }) } + console.log(o) } }, diff --git a/ui/src/SeriesPage.vue b/ui/src/SeriesPage.vue old mode 100644 new mode 100755 index 94c432f7..7944243a --- a/ui/src/SeriesPage.vue +++ b/ui/src/SeriesPage.vue @@ -51,7 +51,34 @@
- + + +
+ + + + + + +
@@ -88,20 +115,7 @@ * Optional/Recommended: If fieldmap/distortion correction will be applied to this image, enter the identical text string from the B0FieldIdentifier field of the sequence(s) used to create the fieldmap/distortion estimation. Leave field blank if unclear.

-
- -
- - - - - -
+ All objects under this series contain the following common metadata. @@ -197,7 +211,7 @@ export default defineComponent({ computed: { ...mapState(['ezbids', 'bidsSchema', 'config']), - ...mapGetters(['getBIDSEntities', 'getURL']), //doesn't work with ts? + ...mapGetters(['getBIDSEntities', 'getBIDSMetadata', 'getURL']), //doesn't work with ts? }, mounted() { @@ -221,6 +235,11 @@ export default defineComponent({ return entities; }, + // getSomeMetadata(type: string): any { + // const metadata = Object.assign({}, this.getBIDSMetadata(type)); + // return metadata; + // }, + toggleInfo(entity: string) { this.showInfo[entity] = !this.showInfo[entity]; }, @@ -266,6 +285,26 @@ export default defineComponent({ } } + // let metadata = this.getBIDSMetadata(s.type); + // for(const [key, value] of Object.entries(metadata)) { + // if(value == "required") { + // s.validationWarnings.push("json sidecar metadata: "+key+" is required."); + + // } + // } + + /* Ensure direction (dir) entity labels are capitalized (e.g. AP, not ap) and match ezBIDS internal PED checks. + Can occur when user adds this themselves. + */ + if(s.entities.direction != "") { + if(s.entities.direction !== s.entities.direction.toUpperCase()) { + s.validationErrors.push("Please ensure that the direction entity label is fully capitalized") + } + if(s.entities.direction.toUpperCase() !== s.PED) { + s.validationWarnings.push(`ezBIDS detects that the direction shoula be ${s.PED}, not ${s.entities.direction}. Please be sure before continuing`) + } + } + if(s.type.startsWith("fmap/")) { if(!s.IntendedFor) s.IntendedFor = []; if(s.IntendedFor.length == 0) { diff --git a/ui/src/libUnsafe.ts b/ui/src/libUnsafe.ts index 8948045b..31ab2d57 100755 --- a/ui/src/libUnsafe.ts +++ b/ui/src/libUnsafe.ts @@ -1,28 +1,8 @@ // @ts-nocheck +import { objectToString } from "@vue/shared"; import { isPlainObject } from "vue/node_modules/@vue/shared"; -// export function setB0FieldSource($root) { - -// $root._organized.forEach(subGroup=>{ -// subGroup.sess.forEach(sesGroup=>{ - -// let protocolObjects = sesGroup.objects - -// // for(const protocol of protocolObjects) { -// // Object.keys(protocol).forEach(key=>{ -// // if(key == "_type" && protocol[key] == "dwi/dwi") { - -// // let funcObjs = protocolObjects.filter(o=>o._type == "func/bold" && o.exclude == false && (!o._entities.mag || o._entities.mag == "mag")) -// // let fmapObjs = protocolObjects.filter(o=>o_type.includes("fmap") && o.exclude == false) -// // if(funcObjs.length && fmapObjs.length) { -// // funcObjs.forEach(funcObj=>{ -// // }) -// // } -// }); -// }); -// } - //deepEqual and isPrimitive functions come from https://stackoverflow.com/a/45683145 export function deepEqual(obj1, obj2) { //Determines if two arrays are equal or not. Better then JSON.stringify @@ -945,7 +925,6 @@ export function createEventObjects(ezbids, files) { randRunID++ } - //update section_id, series_idx, and ModifiedSeriesNumber if(sessions.length > 0) { try { diff --git a/ui/src/store/index.ts b/ui/src/store/index.ts index 2c97a494..182a5b2f 100755 --- a/ui/src/store/index.ts +++ b/ui/src/store/index.ts @@ -3,16 +3,17 @@ import { createStore } from 'vuex' import bidsEntities from '../assets/schema/objects/entities.json' -// import { getFieldSeverity } from 'bids-validator-monorepo/bids-validator/src/schema/applyRules' -// import { getFieldSeverity } from 'bids-validator/schema/applyRules' +// import { getFieldSeverity } from '../../../bids-validator/bids-validator/src/schema/applyRules' -import { BIDSContext } from 'bids-validator/schema/context' +import { BIDSContext } from '../../../bids-validator/bids-validator/src/schema/context' import { GenericRule, GenericSchema, SchemaFields, SchemaTypeLike, -} from 'bids-validator/types/schema' +} from '../../../bids-validator/bids-validator/src/types/schema' +import { Severity } from '../../../bids-validator/bids-validator/src/types/issues' + // export interface GenericRule { // selectors?: string[] @@ -117,6 +118,8 @@ export interface Subject { export interface Series { entities: any; + PED: string; + validationErrors: string[]; validationWarnings: string[]; @@ -226,8 +229,9 @@ interface BIDSEntities { [key: string]: { //task, subject, session, etc.. name: string; entity: string; - format: string; description: string; + type: string; + format: string } } @@ -300,28 +304,28 @@ const state = { ezbids: { notLoaded: true, - //pretty much straight out of bids/dataset_description.json - datasetDescription: { - Name: "", - BIDSVersion: "", - DatasetType: "", - License: "", - Authors: [], - Acknowledgements: "", //"Special thanks to Korbinian Brodmann for help in formatting this dataset in BIDS. We thank Alan Lloyd Hodgkin and Andrew Huxley for helpful comments and discussions about the experiment and manuscript; Hermann Ludwig He lmholtz for administrative support; and Claudius Galenus for providing data for the medial-to-lateral index analysis.", - HowToAcknowledge: "", //"Please cite this paper: https://www.ncbi.nlm.nih.gov/pubmed/001012092119281", - Funding: [ - //"National Institute of Neuroscience Grant F378236MFH1", - //"National Institute of Neuroscience Grant 5RMZ0023106" - ], - EthicsApprovals: [ - //"Army Human Research Protections Office (Protocol ARL-20098-10051, ARL 12-040, and ARL 12-041)" - ], - ReferencesAndLinks: [ - //"https://www.ncbi.nlm.nih.gov/pubmed/001012092119281",items - //"http://doi.org/1920.8/jndata.2015.7" - ], - DatasetDOI: "", //"10.0.2.3/dfjj.10" - } as DatasetDescription, + // //pretty much straight out of bids/dataset_description.json + // datasetDescription: { + // Name: "", + // BIDSVersion: "", + // DatasetType: "", + // License: "", + // Authors: [], + // Acknowledgements: "", //"Special thanks to Korbinian Brodmann for help in formatting this dataset in BIDS. We thank Alan Lloyd Hodgkin and Andrew Huxley for helpful comments and discussions about the experiment and manuscript; Hermann Ludwig He lmholtz for administrative support; and Claudius Galenus for providing data for the medial-to-lateral index analysis.", + // HowToAcknowledge: "", //"Please cite this paper: https://www.ncbi.nlm.nih.gov/pubmed/001012092119281", + // Funding: [ + // //"National Institute of Neuroscience Grant F378236MFH1", + // //"National Institute of Neuroscience Grant 5RMZ0023106" + // ], + // EthicsApprovals: [ + // //"Army Human Research Protections Office (Protocol ARL-20098-10051, ARL 12-040, and ARL 12-041)" + // ], + // ReferencesAndLinks: [ + // //"https://www.ncbi.nlm.nih.gov/pubmed/001012092119281",items + // //"http://doi.org/1920.8/jndata.2015.7" + // ], + // DatasetDOI: "", //"10.0.2.3/dfjj.10" + // } as DatasetDescription, readme: "", participantsColumn: {},