diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..295cf2804 --- /dev/null +++ b/404.html @@ -0,0 +1,813 @@ + + + + + + + + + + + + + + + + + + MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..7d6673243 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +docs.medperf.org \ No newline at end of file diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..9081d1510 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +google_analytics: G-QNE8JMYY88 diff --git a/assets/_mkdocstrings.css b/assets/_mkdocstrings.css new file mode 100644 index 000000000..049a254b9 --- /dev/null +++ b/assets/_mkdocstrings.css @@ -0,0 +1,64 @@ + +/* Avoid breaking parameter names, etc. in table cells. */ +.doc-contents td code { + word-break: normal !important; +} + +/* No line break before first paragraph of descriptions. */ +.doc-md-description, +.doc-md-description>p:first-child { + display: inline; +} + +/* Max width for docstring sections tables. */ +.doc .md-typeset__table, +.doc .md-typeset__table table { + display: table !important; + width: 100%; +} + +.doc .md-typeset__table tr { + display: table-row; +} + +/* Defaults in Spacy table style. */ +.doc-param-default { + float: right; +} + +/* Keep headings consistent. */ +h1.doc-heading, +h2.doc-heading, +h3.doc-heading, +h4.doc-heading, +h5.doc-heading, +h6.doc-heading { + font-weight: 400; + line-height: 1.5; + color: inherit; + text-transform: none; +} + +h1.doc-heading { + font-size: 1.6rem; +} + +h2.doc-heading { + font-size: 1.2rem; +} + +h3.doc-heading { + font-size: 1.15rem; +} + +h4.doc-heading { + font-size: 1.10rem; +} + +h5.doc-heading { + font-size: 1.05rem; +} + +h6.doc-heading { + font-size: 1rem; +} \ No newline at end of file diff --git a/assets/auth/code_confirmation.png b/assets/auth/code_confirmation.png new file mode 100644 index 000000000..f6c2819d1 Binary files /dev/null and b/assets/auth/code_confirmation.png differ diff --git a/assets/auth/login_code.png b/assets/auth/login_code.png new file mode 100644 index 000000000..c1bbb099a Binary files /dev/null and b/assets/auth/login_code.png differ diff --git a/assets/auth/login_email.png b/assets/auth/login_email.png new file mode 100644 index 000000000..6cf5b4a80 Binary files /dev/null and b/assets/auth/login_email.png differ diff --git a/assets/auth/login_success.png b/assets/auth/login_success.png new file mode 100644 index 000000000..599db39bf Binary files /dev/null and b/assets/auth/login_success.png differ diff --git a/assets/auth/login_terminal.png b/assets/auth/login_terminal.png new file mode 100644 index 000000000..5f37db6f7 Binary files /dev/null and b/assets/auth/login_terminal.png differ diff --git a/assets/components.png b/assets/components.png new file mode 100644 index 000000000..60493a6b6 Binary files /dev/null and b/assets/components.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.220ee61c.min.js b/assets/javascripts/bundle.220ee61c.min.js new file mode 100644 index 000000000..116072a11 --- /dev/null +++ b/assets/javascripts/bundle.220ee61c.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Ci=Object.create;var gr=Object.defineProperty;var Ri=Object.getOwnPropertyDescriptor;var ki=Object.getOwnPropertyNames,Ht=Object.getOwnPropertySymbols,Hi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,nn=Object.prototype.propertyIsEnumerable;var rn=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&rn(e,r,t[r]);if(Ht)for(var r of Ht(t))nn.call(t,r)&&rn(e,r,t[r]);return e};var on=(e,t)=>{var r={};for(var n in e)yr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Ht)for(var n of Ht(e))t.indexOf(n)<0&&nn.call(e,n)&&(r[n]=e[n]);return r};var Pt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Pi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ki(t))!yr.call(e,o)&&o!==r&&gr(e,o,{get:()=>t[o],enumerable:!(n=Ri(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Ci(Hi(e)):{},Pi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var sn=Pt((xr,an)=>{(function(e,t){typeof xr=="object"&&typeof an!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(O){return!!(O&&O!==document&&O.nodeName!=="HTML"&&O.nodeName!=="BODY"&&"classList"in O&&"contains"in O.classList)}function f(O){var Qe=O.type,De=O.tagName;return!!(De==="INPUT"&&s[Qe]&&!O.readOnly||De==="TEXTAREA"&&!O.readOnly||O.isContentEditable)}function c(O){O.classList.contains("focus-visible")||(O.classList.add("focus-visible"),O.setAttribute("data-focus-visible-added",""))}function u(O){O.hasAttribute("data-focus-visible-added")&&(O.classList.remove("focus-visible"),O.removeAttribute("data-focus-visible-added"))}function p(O){O.metaKey||O.altKey||O.ctrlKey||(a(r.activeElement)&&c(r.activeElement),n=!0)}function m(O){n=!1}function d(O){a(O.target)&&(n||f(O.target))&&c(O.target)}function h(O){a(O.target)&&(O.target.classList.contains("focus-visible")||O.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(O.target))}function v(O){document.visibilityState==="hidden"&&(o&&(n=!0),Y())}function Y(){document.addEventListener("mousemove",N),document.addEventListener("mousedown",N),document.addEventListener("mouseup",N),document.addEventListener("pointermove",N),document.addEventListener("pointerdown",N),document.addEventListener("pointerup",N),document.addEventListener("touchmove",N),document.addEventListener("touchstart",N),document.addEventListener("touchend",N)}function B(){document.removeEventListener("mousemove",N),document.removeEventListener("mousedown",N),document.removeEventListener("mouseup",N),document.removeEventListener("pointermove",N),document.removeEventListener("pointerdown",N),document.removeEventListener("pointerup",N),document.removeEventListener("touchmove",N),document.removeEventListener("touchstart",N),document.removeEventListener("touchend",N)}function N(O){O.target.nodeName&&O.target.nodeName.toLowerCase()==="html"||(n=!1,B())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),Y(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var cn=Pt(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},s=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(B,N){d.append(N,B)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(O){throw new Error("URL unable to set base "+c+" due to "+O)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,Y=!0,B=this;["append","delete","set"].forEach(function(O){var Qe=h[O];h[O]=function(){Qe.apply(h,arguments),v&&(Y=!1,B.search=h.toString(),Y=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var N=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==N&&(N=this.search,Y&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},s=i.prototype,a=function(f){Object.defineProperty(s,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){a(f)}),Object.defineProperty(s,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(s,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var qr=Pt((Mt,Nr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof Nr=="object"?Nr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return Ai}});var s=i(279),a=i.n(s),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(T){return!1}}var d=function(T){var E=p()(T);return m("cut"),E},h=d;function v(j){var T=document.documentElement.getAttribute("dir")==="rtl",E=document.createElement("textarea");E.style.fontSize="12pt",E.style.border="0",E.style.padding="0",E.style.margin="0",E.style.position="absolute",E.style[T?"right":"left"]="-9999px";var H=window.pageYOffset||document.documentElement.scrollTop;return E.style.top="".concat(H,"px"),E.setAttribute("readonly",""),E.value=j,E}var Y=function(T,E){var H=v(T);E.container.appendChild(H);var I=p()(H);return m("copy"),H.remove(),I},B=function(T){var E=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},H="";return typeof T=="string"?H=Y(T,E):T instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(T==null?void 0:T.type)?H=Y(T.value,E):(H=p()(T),m("copy")),H},N=B;function O(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?O=function(E){return typeof E}:O=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},O(j)}var Qe=function(){var T=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=T.action,H=E===void 0?"copy":E,I=T.container,q=T.target,Me=T.text;if(H!=="copy"&&H!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&O(q)==="object"&&q.nodeType===1){if(H==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(H==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return N(Me,{container:I});if(q)return H==="cut"?h(q):N(q,{container:I})},De=Qe;function $e(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?$e=function(E){return typeof E}:$e=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},$e(j)}function Ei(j,T){if(!(j instanceof T))throw new TypeError("Cannot call a class as a function")}function tn(j,T){for(var E=0;E0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=$e(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var q=this;this.listener=c()(I,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(I){var q=I.delegateTarget||I.currentTarget,Me=this.action(q)||"copy",kt=De({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(kt?"success":"error",{action:Me,text:kt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(I){return vr("action",I)}},{key:"defaultTarget",value:function(I){var q=vr("target",I);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(I){return vr("text",I)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(I){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return N(I,q)}},{key:"cut",value:function(I){return h(I)}},{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof I=="string"?[I]:I,Me=!!document.queryCommandSupported;return q.forEach(function(kt){Me=Me&&!!document.queryCommandSupported(kt)}),Me}}]),E}(a()),Ai=Li},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,f){for(;a&&a.nodeType!==o;){if(typeof a.matches=="function"&&a.matches(f))return a;a=a.parentNode}}n.exports=s},438:function(n,o,i){var s=i(828);function a(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof m=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=s(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(n,o,i){var s=i(879),a=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(h))throw new TypeError("Third argument must be a Function");if(s.node(m))return c(m,d,h);if(s.nodeList(m))return u(m,d,h);if(s.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return a(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),s=f.toString()}return s}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,s,a){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var f=this;function c(){f.off(i,c),s.apply(a,arguments)}return c._=s,this.on(i,c,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=a.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var rs=/["'&<>]/;Yo.exports=ns;function ns(e){var t=""+e,r=rs.exec(t);if(!r)return t;var n,o="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],s;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(a){s={error:a}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||a(m,d)})})}function a(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof et?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){a("next",m)}function u(m){a("throw",m)}function p(m,d){m(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function pn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(s){return new Promise(function(a,f){s=e[i](s),o(a,f,s.done,s.value)})}}function o(i,s,a,f){Promise.resolve(f).then(function(c){i({value:c,done:a})},s)}}function C(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var It=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),f=a.next();!f.done;f=a.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var u=this.initialTeardown;if(C(u))try{u()}catch(v){i=v instanceof It?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=Ee(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{ln(h)}catch(v){i=i!=null?i:[],v instanceof It?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new It(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ln(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Sr=Ie.EMPTY;function jt(e){return e instanceof Ie||e&&"closed"in e&&C(e.remove)&&C(e.add)&&C(e.unsubscribe)}function ln(e){C(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,s=o.isStopped,a=o.observers;return i||s?Sr:(this.currentObservers=null,a.push(r),new Ie(function(){n.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,s=n.isStopped;o?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,n){return new xn(r,n)},t}(F);var xn=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Sr},t}(x);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,s=n._infiniteTimeWindow,a=n._timestampProvider,f=n._windowTime;o||(i.push(r),!s&&i.push(a.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,s=o._buffer,a=s.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var s=r.actions;n!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Wt);var Sn=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Dt);var Oe=new Sn(wn);var M=new F(function(e){return e.complete()});function Vt(e){return e&&C(e.schedule)}function Cr(e){return e[e.length-1]}function Ye(e){return C(Cr(e))?e.pop():void 0}function Te(e){return Vt(Cr(e))?e.pop():void 0}function zt(e,t){return typeof Cr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Nt(e){return C(e==null?void 0:e.then)}function qt(e){return C(e[ft])}function Kt(e){return Symbol.asyncIterator&&C(e==null?void 0:e[Symbol.asyncIterator])}function Qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Yt=zi();function Gt(e){return C(e==null?void 0:e[Yt])}function Bt(e){return un(this,arguments,function(){var r,n,o,i;return $t(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,et(r.read())];case 3:return n=s.sent(),o=n.value,i=n.done,i?[4,et(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,et(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Jt(e){return C(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(qt(e))return Ni(e);if(pt(e))return qi(e);if(Nt(e))return Ki(e);if(Kt(e))return On(e);if(Gt(e))return Qi(e);if(Jt(e))return Yi(e)}throw Qt(e)}function Ni(e){return new F(function(t){var r=e[ft]();if(C(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function qi(e){return new F(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?A(function(o,i){return e(o,i,n)}):de,ge(1),r?He(t):Dn(function(){return new Zt}))}}function Vn(){for(var e=[],t=0;t=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,f=a===void 0?!0:a;return function(c){var u,p,m,d=0,h=!1,v=!1,Y=function(){p==null||p.unsubscribe(),p=void 0},B=function(){Y(),u=m=void 0,h=v=!1},N=function(){var O=u;B(),O==null||O.unsubscribe()};return y(function(O,Qe){d++,!v&&!h&&Y();var De=m=m!=null?m:r();Qe.add(function(){d--,d===0&&!v&&!h&&(p=$r(N,f))}),De.subscribe(Qe),!u&&d>0&&(u=new rt({next:function($e){return De.next($e)},error:function($e){v=!0,Y(),p=$r(B,o,$e),De.error($e)},complete:function(){h=!0,Y(),p=$r(B,s),De.complete()}}),U(O).subscribe(u))})(c)}}function $r(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function z(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),V(e===_e()),J())}function Xe(e){return{x:e.offsetLeft,y:e.offsetTop}}function Kn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>Xe(e)),V(Xe(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>rr(e)),V(rr(e)))}var Yn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Wr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),va?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Wr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ba.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Gn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Jn=typeof WeakMap!="undefined"?new WeakMap:new Yn,Xn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ga.getInstance(),n=new La(t,r,this);Jn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Xn.prototype[e]=function(){var t;return(t=Jn.get(this))[e].apply(t,arguments)}});var Aa=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:Xn}(),Zn=Aa;var eo=new x,Ca=$(()=>k(new Zn(e=>{for(let t of e)eo.next(t)}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ca.pipe(S(t=>t.observe(e)),g(t=>eo.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var to=new x,Ra=$(()=>k(new IntersectionObserver(e=>{for(let t of e)to.next(t)},{threshold:0}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function sr(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function ro(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),J())}var cr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function no(e){return cr[e].checked}function Ke(e,t){cr[e].checked!==t&&cr[e].click()}function Ue(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function ka(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ha(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function oo(){let e=b(window,"keydown").pipe(A(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:no("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),A(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!ka(n,r)}return!0}),pe());return Ha().pipe(g(t=>t?M:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function io(){return new x}function ao(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)ao(e,r)}function _(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)ao(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function so(){return location.hash.substring(1)}function Dr(e){let t=_("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Pa(e){return L(b(window,"hashchange"),e).pipe(l(so),V(so()),A(t=>t.length>0),X(1))}function co(e){return Pa(e).pipe(l(t=>ce(`[id="${t}"]`)),A(t=>typeof t!="undefined"))}function Vr(e){let t=matchMedia(e);return er(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function fo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function zr(e,t){return e.pipe(g(r=>r?t():M))}function ur(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>M),g(r=>r.status!==200?Ot(()=>new Error(r.statusText)):k(r)))}function We(e,t){return ur(e,t).pipe(g(r=>r.json()),X(1))}function uo(e,t){let r=new DOMParser;return ur(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),X(1))}function pr(e){let t=_("script",{src:e});return $(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(g(()=>Ot(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function po(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function lo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(po),V(po()))}function mo(){return{width:innerWidth,height:innerHeight}}function ho(){return b(window,"resize",{passive:!0}).pipe(l(mo),V(mo()))}function bo(){return G([lo(),ho()]).pipe(l(([e,t])=>({offset:e,size:t})),X(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(ee("size")),o=G([n,r]).pipe(l(()=>Xe(e)));return G([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:f,y:c}])=>({offset:{x:s.x-f,y:s.y-c+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,f,c)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:f,error:c});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

In Progress

+

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/concepts/associations/index.html b/concepts/associations/index.html new file mode 100644 index 000000000..26296df23 --- /dev/null +++ b/concepts/associations/index.html @@ -0,0 +1,876 @@ + + + + + + + + + + + + + + + + + + + + In Progress - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

In Progress

+

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/concepts/auth/index.html b/concepts/auth/index.html new file mode 100644 index 000000000..f66b450cc --- /dev/null +++ b/concepts/auth/index.html @@ -0,0 +1,1009 @@ + + + + + + + + + + + + + + + + + + + + + + + + Authentication - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Authentication

+

This guide helps you learn how to login and logout using the MedPerf client to access the main production MedPerf server. MedPerf uses passwordless authentication. This means that login will only require you to access your email in order complete the login process.

+

Login

+

Follow the steps below to login:

+
    +
  • Step1 Run the following command:
  • +
+
medperf auth login
+
+

You will be prompted to enter your email address.

+

After entering your email address, you will be provided with a verification URL and a code. A text similar to the following will be printed in your terminal:

+

login_terminal

+
+

Tip

+

If you are running the MedPerf client on a machine with no graphical interface, you can use the link on any other device, e.g. your cellphone. Make sure that you trust that device.

+
+
    +
  • Step2 Open the verification URL and confirm the code:
  • +
+

Open the printed URL in your browser. You will be presented with a code, and you will be asked to confirm if that code is the same one printed in your terminal.

+

Code Confirmation

+
    +
  • Step3 After confirmation, you will be asked to enter your email address. Enter your email address and press "Continue". You will see the following screen:
  • +
+

Login code

+
    +
  • Step4 Check your inbox. You should receive an email similar to the following:
  • +
+

Login email

+

Enter the received code in the previous screen.

+
    +
  • Step5 If there is no problem with your account, the login will be successful, and you will see a screen similar to the following:
  • +
+

Login success

+

Logout

+

To disconnect the MedPerf client, simply run the following command:

+
medperf auth logout
+
+

Checking the authentication status

+

Note that when you log in, the MedPerf client will remember you as long as you are using the same profile. If you switch to another profile by running medperf profile activate <other-profile>, you may have to log in again. If you switch back again to a profile where you previously logged in, your login state will be restored.

+ + +

You can always check the current login status by the running the following command:

+
medperf auth status
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/concepts/hosting_files/index.html b/concepts/hosting_files/index.html new file mode 100644 index 000000000..17d1e781e --- /dev/null +++ b/concepts/hosting_files/index.html @@ -0,0 +1,1018 @@ + + + + + + + + + + + + + + + + + + + + + + Hosting Files - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Hosting Files

+

MedPerf requires some files to be hosted on the cloud when running machine learning pipelines. Submitting MLCubes to the MedPerf server means submitting their metadata, and not, for example, model weights or parameters files. MLCube files such as model weights need to be hosted on the cloud, and the submitted MLCube metadata will only contain URLs (or certain identifiers) for these files. Another example would be benchmark submission, where demo datasets need to be hosted.

+

The MedPerf client expects files to be hosted in certain ways. Below are options of how files can be hosted and how MedPerf identitfies them (e.g. a URL).

+

File hosting

+

This can be done with any cloud hosting tool/provider you desire (such as GCP, AWS, Dropbox, Google Drive, Github). As long as your file can be accessed through a direct download link, it should work with medperf. Generating a direct download link for your hosted file can be straight-forward when using some providers (e.g. Amazon Web Services, Google Cloud Platform, Microsoft Azure) and can be a bit tricky when using others (e.g. Dropbox, GitHub, Google Drive).

+
+

Note

+

Direct download links must be permanent

+
+
+

Tip

+

You can make sure if a URL is a direct download link or not using tools like wget or curl. Running wget <URL> will download the file if the URL is a direct download link. Running wget <URL> may fail or may download an HTML page if the URL is not a direct download link.

+
+

When your file is hosted with a direct download link, MedPerf will be able to identify this file using that direct download link. So for example, when you are submitting an MLCube, you would pass your hosted MLCube manifest file as follows:

+
--mlcube-file <the-direct-download-link-to-the-file>
+
+
+

Warning

+

Files in this case are supposed to have anonymous public read access permission.

+
+ +

It was a common practice by the current MedPerf users to host files on GitHub. You can learn below how to find the direct download link of a file hosted on GitHub. You can check online for other storage providers.

+

It's important though to make sure the files won't be modified after being submitted to medperf, which could happen due to future commits. Because of this, the URLs of the files hosted on GitHub must contain a reference to the current commit hash. Below are the steps to get this URL for a specific file:

+
    +
  1. Open the GitHub repository and ensure you are in the correct branch
  2. +
  3. Click on “Commits” at the right top corner of the repository explorer.
  4. +
  5. Locate the latest commit, it is the top most commit.
  6. +
  7. If you are targeting previous versions of your file, make sure to consider the right commit.
  8. +
  9. Click on this button “<>” corresponding to the commit (Browse the repository at this point in the history).
  10. +
  11. Navigate to the file of interest.
  12. +
  13. Click on “Raw”.
  14. +
  15. Copy the url from your browser. It should be a UserContent GitHub URLs (domain raw.githubusercontent.com).
  16. +
+

Synapse hosting

+

You can choose the option of hosting with Synapse in cases where privacy is a concern. Please refer to this link for hosting files on the Synapse platform.

+

When your file is hosted on Synapse, MedPerf will be able to identify this file using the Synapse ID corresponding to that file. So for example, when you are submitting an MLCube, you would pass your hosted MLCube manifest file as follows (note the prefix):

+
--mlcube-file synapse:<the-synapse-id-of-the-file>
+
+

Note that you need to authenticate with your Synapse credentials if you plan to use a Synaspe file with MedPerf. To do so run medperf auth synapse_login.

+
+

Note

+

You must authenticate if using files on Synapse. If this is not necessary, this means the file has anonymous public access read permission. In this case, Synapse allows you to generate a permanent direct download link for your file and you can follow the previous section.

+
+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/concepts/mlcube_files/index.html b/concepts/mlcube_files/index.html new file mode 100644 index 000000000..8c7db7b74 --- /dev/null +++ b/concepts/mlcube_files/index.html @@ -0,0 +1,1077 @@ + + + + + + + + + + + + + + + + + + + + + + + + MLCube Components - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

MLCube Components: What to Host?

+

Once you have built an MLCube ready for MedPerf, you need to host it somewhere on the cloud so that it can be identified and retrieved by the MedPerf client on other machines. This requires hosting the MLCube components somewhere on the cloud. The following is a description of what needs to be hosted.

+

Hosting Your Container Image

+

MLCubes execute a container image behind the scenes. This container image is usually hosted on a container registry, like Docker Hub. In cases where this is not possible, medperf provides the option of passing the image file directly (i.e. having the image file hosted somewhere and providing MedPerf with the download link). MLCubes that work with images outside of the docker registry usually store the image inside the <path_to_mlcube>/workspace/.image folder. MedPerf supports using direct container image files for Singularity only.

+
+

Note

+

While there is the option of hosting the singularity image directly, it is highly recommended to use a container registry for accessability and usability purposes. MLCube also has mechanisms for converting containers to other runners, like Docker to Singularity.

+
+
+

Note

+

Docker Images can be on any docker container registry, not necessarily on Docker Hub.

+
+

Files to be hosted

+

The following is the list of files that must be hosted separately so they can be used by MedPerf:

+

mlcube.yaml

+

Every MLCube is defined by its mlcube.yaml manifest file. As such, Medperf needs to have access to this file to recreate the MLCube. This file can be found inside your MLCube at <path_to_mlcube>/mlcube.yaml.

+

parameters.yaml (Optional)

+

The parameters.yaml file specify additional ways to parametrize your model MLCube using the same container image it is built with. This file can be found inside your MLCube at <path_to_mlcube>/workspace/parameters.yaml.

+

additional_files.tar.gz (Optional)

+

MLCubes may require additional files that may be desired to keep separate from the model architecture and hosted image. For example, model weights. This allows for testing multiple implementations of the same model, without requiring a separate container image for each. If additional images are being used by your MLCube, they need to be compressed into a .tar.gz file and hosted separately. You can create this tarball file with the following command

+
tar -czf additional_files.tar.gz -C <path_to_mlcube>/workspace/additional_files .
+
+

Preparing an MLCube for hosting

+

To facilitate hosting and interface compatibility validation, MedPerf provides a script that finds all the required assets, compresses them if necessary, and places them in a single location for easy access. To run the script, make sure you have medperf installed and you are in medperf's root directory:

+
python scripts/package-mlcube.py \
+   --mlcube path/to/mlcube \
+   --mlcube-types <list-of-comma-separated-strings> \
+   --output path/to/file.tar.gz
+
+

where:

+
    +
  • path/to/mlcube is the path to the MLCube folder containing the manifest file (mlcube.yaml)
  • +
  • --mlcube-types specifies a comma-separated list of MLCube types ('data-preparator' for a data preparation MLCube, 'model' for a model MLCube, and 'metrics' for a metrics MLCube.)
  • +
  • path/to/file.tar.gz is a path to the output file where you want to store the compressed version of all assets.
  • +
+

See python scripts/package-mlcube.py --help for more information.

+

Once executed, you should be able to find all prepared assets at ./mlcube/assets, as well as a compressed version of the assets folder at the output path provided.

+
+

Note

+

The --output parameter is optional. The compressed version of the assets folder can be useful in cases where you don't directly interact with the MedPerf server, but instead you do so through a third party. This is usually the case for challenges and competitions.

+
+

See Also

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/concepts/priorities/index.html b/concepts/priorities/index.html new file mode 100644 index 000000000..478c68f26 --- /dev/null +++ b/concepts/priorities/index.html @@ -0,0 +1,876 @@ + + + + + + + + + + + + + + + + + + + + In Progress - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

In Progress

+

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/concepts/profiles/index.html b/concepts/profiles/index.html new file mode 100644 index 000000000..4a1aaee8e --- /dev/null +++ b/concepts/profiles/index.html @@ -0,0 +1,876 @@ + + + + + + + + + + + + + + + + + + + + In Progress - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

In Progress

+

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/concepts/single_run/index.html b/concepts/single_run/index.html new file mode 100644 index 000000000..0ddc3ac19 --- /dev/null +++ b/concepts/single_run/index.html @@ -0,0 +1,876 @@ + + + + + + + + + + + + + + + + + + + + In Progress - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

In Progress

+

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/gen_ref_pages.py b/gen_ref_pages.py new file mode 100644 index 000000000..8672d867f --- /dev/null +++ b/gen_ref_pages.py @@ -0,0 +1,46 @@ +# Code taken from https://mkdocstrings.github.io/recipes/ + +"""Generate the code reference pages and navigation.""" + +from pathlib import Path + +import mkdocs_gen_files + +nav = mkdocs_gen_files.Nav() + +build_params = [ + ("cli/medperf", "cli/medperf", "cli/medperf", "reference"), + # ("server", "server", "", "reference"), +] + +exclude_paths = ["tests", "templates", "logging"] + +for path, mod, doc, full_doc in build_params: + for path in sorted(Path(path).rglob("*.py")): + module_path = path.relative_to(mod).with_suffix("") + doc_path = path.relative_to(doc).with_suffix(".md") + full_doc_path = Path(full_doc, doc_path) + + parts = tuple(module_path.parts) + + if parts[-1] == "__init__": + parts = parts[:-1] + continue + elif parts[-1] in ["__main__", "setup"]: + continue + if parts == (): + continue + if parts[0] in exclude_paths: + continue + + nav[parts] = str(doc_path) # + + with mkdocs_gen_files.open(full_doc_path, "w") as fd: + ident = ".".join(parts) + fd.write(f"::: {ident}") + + mkdocs_gen_files.set_edit_path(full_doc_path, path) + +with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: # + + nav_file.writelines(nav.build_literate_nav()) # diff --git a/getting_started/benchmark_owner_demo/index.html b/getting_started/benchmark_owner_demo/index.html new file mode 100644 index 000000000..a3baac5e0 --- /dev/null +++ b/getting_started/benchmark_owner_demo/index.html @@ -0,0 +1,1640 @@ + + + + + + + + + + + + + + + + + + + + + + + + Bechmark Committee - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + +
+
+
+ + + + + + + + + +
+
+ + + + + + + +

Overview

+

Hands-on Tutorial for Bechmark Committee

+

Overview

+

In this guide, you will learn how a user can use MedPerf to create a benchmark. The key tasks can be summarized as follows:

+
    +
  1. Implement a valid workflow.
  2. +
  3. Develop a demo dataset.
  4. +
  5. Test your workflow.
  6. +
  7. Submitting the MLCubes to the MedPerf server.
  8. +
  9. Host the demo dataset.
  10. +
  11. Submit the benchmark to the MedPerf server.
  12. +
+

It's assumed that you have already set up the general testing environment as explained in the installation and setup guide.

+

Before You Start

+

First steps

+
Running in cloud via Github Codespaces
+

As the most easy way to play with the tutorials you can launch a preinstalled Codespace cloud environment for MedPerf by clicking this link:

+

Open in GitHub Codespaces

+
Running in local environment
+

To start experimenting with MedPerf through this tutorial on your local machine, you need to start by following these quick steps:

+
    +
  1. Install Medperf
  2. +
  3. Set up Medperf
  4. +
+

Prepare the Local MedPerf Server

+

For the purpose of the tutorial, you have to initialize a local MedPerf server with a fresh database and then create the necessary entities that you will be interacting with. To do so, run the following: (make sure you are in MedPerf's root folder)

+
cd server
+sh reset_db.sh
+python seed.py --demo benchmark
+cd ..
+
+

Download the Necessary files

+

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

+
sh tutorials_scripts/setup_benchmark_tutorial.sh
+
+

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. The folder contains the following content:

+
+Toy content description +

In this tutorial we will create a benchmark that classifies chest X-Ray images.

+

Demo Data

+

The medperf_tutorial/demo_data/ folder contains the demo dataset content.

+
    +
  • images/ folder includes sample images.
  • +
  • labels/labels.csv provides a basic ground truth markup, indicating the class each image belongs to.
  • +
+

The demo dataset is a sample dataset used for the development of your benchmark and used by Model Owners for the development of their models. More details are available in the section below

+

Data Preparator MLCube

+

The medperf_tutorial/data_preparator/ contains a DataPreparator MLCube that you must implement. This MLCube: + - Transforms raw data into a format convenient for model consumption, such as converting DICOM images into numpy tensors, cropping patches, normalizing columns, etc. It's up to you to define the format that is handy for future models. + - Ensures its output is in a standardized format, allowing Model Owners/Developers to rely on its consistency.

+

Model MLCube

+

The medperf_tutorial/model_custom_cnn/ is an example of a Model MLCube. You need to implement a reference model which will be used by data owners to test the compatibility of their data with your pipeline. Also, Model Developers joining your benchmark will follow the input/output specifications of this model when building their own models.

+

Metrics MLCube

+

The medperf_tutorial/metrics/ houses a Metrics MLCube that processes ground truth data, model predictions, and computes performance metrics - such as classification accuracy, loss, etc. After a Dataset Owner runs the benchmark pipeline on their data, these final metric values will be shared with you as the Benchmark Owner.

+
+

In real life all the listed artifacts and files have to be created on your own. However, for tutorial's sake you may use this toy data.

+

Login to the Local MedPerf Server

+

The local MedPerf server is pre-configured with a dummy local authentication system. Remember that when you are communicating with the real MedPerf server, you should follow the steps in this guide to login. For the tutorials, you should not do anything.

+

You are now ready to start!

+

1. Implement a Valid Workflow

+

Benchmark Committee implements MLCubes for workflow +The implementation of a valid workflow is accomplished by implementing three MLCubes:

+
    +
  1. +

    Data Preparator MLCube: This MLCube will transform raw data into a dataset ready for the AI model execution. All data owners willing to participate in this benchmark will have their data prepared using this MLCube. A guide on how to implement data preparation MLCubes can be found here.

    +
  2. +
  3. +

    Reference Model MLCube: This MLCube will contain an example model implementation for the desired AI task. It should be compatible with the data preparation MLCube (i.e., the outputs of the data preparation MLCube can be directly fed as inputs to this MLCube). A guide on how to implement model MLCubes can be found here.

    +
  4. +
  5. +

    Metrics MLCube: This MLCube will be responsible for evaluating the performance of a model. It should be compatible with the reference model MLCube (i.e., the outputs of the reference model MLCube can be directly fed as inputs to this MLCube). A guide on how to implement metrics MLCubes can be found here.

    +
  6. +
+

For this tutorial, you are provided with following three already implemented mlcubes for the task of chest X-ray classification. The implementations can be found in the following links: Data Preparator, Reference Model, Metrics. These mlcubes are setup locally for you and can be found in your workspace folder under data_preparator, model_custom_cnn, and metrics.

+

2. Develop a Demo Dataset

+

Benchmark Committee develops a demo dataset +A demo dataset is a small reference dataset. It contains a few data records and their labels, which will be used to test the benchmark's workflow in two scenarios:

+
    +
  1. +

    It is used for testing the benchmark's default workflow. The MedPerf client automatically runs a compatibility test of the benchmark's three mlcubes prior to its submission. The test is run using the benchmark's demo dataset as input.

    +
  2. +
  3. +

    When a model owner wants to participate in the benchmark, the MedPerf client tests the compatibility of their model with the benchmark's data preparation cube and metrics cube. The test is run using the benchmark's demo dataset as input.

    +
  4. +
+

For this tutorial, you are provided with a demo dataset for the chest X-ray classification workflow. The dataset can be found in your workspace folder under demo_data. It is a small dataset comprising two chest X-ray images and corresponding thoracic disease labels.

+

You can test the workflow now that you have the three MLCubes and the demo data. Testing the workflow before submitting any asset to the MedPerf server is usually recommended.

+

3. Test your Workflow

+

MedPerf provides a single command to test an inference workflow. To test your workflow with local MLCubes and local data, the following need to be passed to the command:

+
    +
  1. Path to the data preparation MLCube manifest file: medperf_tutorial/data_preparator/mlcube/mlcube.yaml.
  2. +
  3. Path to the model MLCube manifest file: medperf_tutorial/model_custom_cnn/mlcube/mlcube.yaml.
  4. +
  5. Path to the metrics MLCube manifest file: medperf_tutorial/metrics/mlcube/mlcube.yaml.
  6. +
  7. Path to the demo dataset data records: medperf_tutorial/demo_data/images.
  8. +
  9. Path to the demo dataset data labels. medperf_tutorial/demo_data/labels.
  10. +
+

Run the following command to execute the test ensuring you are in MedPerf's root folder:

+
medperf test run \
+   --data_preparation "medperf_tutorial/data_preparator/mlcube/mlcube.yaml" \
+   --model "medperf_tutorial/model_custom_cnn/mlcube/mlcube.yaml" \
+   --evaluator "medperf_tutorial/metrics/mlcube/mlcube.yaml" \
+   --data_path "medperf_tutorial/demo_data/images" \
+   --labels_path "medperf_tutorial/demo_data/labels"
+
+

Assuming the test passes successfully, you are ready to submit the MLCubes to the MedPerf server.

+

4. Host the Demo Dataset

+

The demo dataset should be packaged in a specific way as a compressed tarball file. The folder stucture in the workspace currently looks like the following:

+
.
+└── medperf_tutorial
+    ├── demo_data
+    │   ├── images
+    │   └── labels
+    │
+    ...
+
+

The goal is to package the folder demo_data. You must first create a file called paths.yaml. This file will provide instructions on how to locate the data records path and the labels path. The paths.yaml file should specify both the data records path and the labels path.

+

In your workspace directory (medperf_tutorial), create a file paths.yaml and fill it with the following:

+
data_path: demo_data/images
+labels_path: demo_data/labels
+
+
+

Note

+

The paths are determined by the Data Preparator MLCube's expected input path.

+
+

After that, the workspace should look like the following:

+
.
+└── medperf_tutorial
+    ├── demo_data
+    │   ├── images
+    │   └── labels
+    ├── paths.yaml
+    │
+    ...
+
+

Finally, compress the required assets (demo_data and paths.yaml) into a tarball file by running the following command:

+
cd medperf_tutorial
+tar -czf demo_data.tar.gz demo_data paths.yaml
+cd ..
+
+

And that's it! Now you have to host the tarball file (demo_data.tar.gz) on the internet.

+

For the tutorial to run smoothly, the file is already hosted at the following URL:

+
https://storage.googleapis.com/medperf-storage/chestxray_tutorial/demo_data.tar.gz
+
+

If you wish to host it by yourself, you can find the list of supported options and details about hosting files in this page.

+

Finally, now after having the MLCubes submitted and the demo dataset hosted, you can submit the benchmark to the MedPerf server.

+

5. Submitting the MLCubes

+

Benchmark Committee submits MLCubes

+

How does MedPerf Recognize an MLCube?

+

The MedPerf server registers an MLCube as metadata comprised of a set of files that can be retrieved from the internet. This means that before submitting an MLCube you have to host its files on the internet. The MedPerf client provides a utility to prepare the files of an MLCube that need to be hosted. You can refer to this page if you want to understand what the files are, but using the utility script is enough.

+

To prepare the files of the three MLCubes, run the following command ensuring you are in MedPerf's root folder:

+
python scripts/package-mlcube.py --mlcube medperf_tutorial/data_preparator/mlcube --mlcube-types data-preparator
+python scripts/package-mlcube.py --mlcube medperf_tutorial/model_custom_cnn/mlcube --mlcube-types model
+python scripts/package-mlcube.py --mlcube medperf_tutorial/metrics/mlcube --mlcube-types metrics
+
+

For each MLCube, this script will create a new folder named assets in the MLCube directory. This folder will contain all the files that should be hosted separately.

+

Host the Files

+

For the tutorial to run smoothly, the files are already hosted. If you wish to host them by yourself, you can find the list of supported options and details about hosting files in this page.

+

Submit the MLCubes

+

Data Preparator MLCube

+

Benchmark Committee submits the Data Preparator MLCube +For the Data Preparator MLCube, the submission should include:

+
    +
  • +

    The URL to the hosted mlcube manifest file, which is:

    +
    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/mlcube.yaml
    +
    +
  • +
  • +

    The URL to the hosted mlcube parameters file, which is:

    +
    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/workspace/parameters.yaml
    +
    +
  • +
+

Use the following command to submit:

+
medperf mlcube submit \
+    --name my-prep-cube \
+    --mlcube-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/mlcube.yaml" \
+    --parameters-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/workspace/parameters.yaml" \
+    --operational
+
+

Reference Model MLCube

+

Benchmark Committee submits the reference Model MLCube +For the Reference Model MLCube, the submission should include:

+
    +
  • +

    The URL to the hosted mlcube manifest file:

    +
    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/mlcube.yaml
    +
    +
  • +
  • +

    The URL to the hosted mlcube parameters file:

    +
    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/workspace/parameters.yaml
    +
    +
  • +
  • +

    The URL to the hosted additional files tarball file:

    +
    https://storage.googleapis.com/medperf-storage/chestxray_tutorial/cnn_weights.tar.gz
    +
    +
  • +
+

Use the following command to submit:

+
medperf mlcube submit \
+--name my-modelref-cube \
+--mlcube-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/mlcube.yaml" \
+--parameters-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/workspace/parameters.yaml" \
+--additional-file "https://storage.googleapis.com/medperf-storage/chestxray_tutorial/cnn_weights.tar.gz" \
+--operational
+
+

Metrics MLCube

+

Benchmark Committee submits the Evaluation Metrics MLCube +For the Metrics MLCube, the submission should include:

+
    +
  • +

    The URL to the hosted mlcube manifest file:

    +
    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/mlcube.yaml
    +
    +
  • +
  • +

    The URL to the hosted mlcube parameters file:

    +
    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/workspace/parameters.yaml
    +
    +
  • +
+

Use the following command to submit:

+
medperf mlcube submit \
+--name my-metrics-cube \
+--mlcube-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/mlcube.yaml" \
+--parameters-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/workspace/parameters.yaml" \
+--operational
+
+

Each of the three MLCubes will be assigned by a server UID. You can check the server UID for each MLCube by running:

+
medperf mlcube ls --mine
+
+

Next, you will learn how to host the demo dataset.

+

6. Submit your Benchmark

+

Benchmark Committee submits the Benchmark Metadata +You need to keep at hand the following information:

+
    +
  • The Demo Dataset URL. Here, the URL will be:
  • +
+
https://storage.googleapis.com/medperf-storage/chestxray_tutorial/demo_data.tar.gz
+
+
    +
  • The server UIDs of the three MLCubes can be found by running:
  • +
+
 medperf mlcube ls
+
+
    +
  • For this tutorial, the UIDs are as follows:
  • +
  • Data preparator UID: 1
  • +
  • Reference model UID: 2
  • +
  • Evaluator UID: 3
  • +
+

You can create and submit your benchmark using the following command:

+
medperf benchmark submit \
+   --name tutorial_bmk \
+   --description "MedPerf demo bmk" \
+   --demo-url "https://storage.googleapis.com/medperf-storage/chestxray_tutorial/demo_data.tar.gz" \
+   --data-preparation-mlcube 1 \
+   --reference-model-mlcube 2 \
+   --evaluator-mlcube 3 \
+   --operational
+
+

The MedPerf client will first automatically run a compatibility test between the MLCubes using the demo dataset. If the test is successful, the benchmark will be submitted along with the compatibility test results.

+
+

Note

+

The benchmark will stay inactive until the MedPerf server admin approves your submission.

+
+

That's it! You can check your benchmark's server UID by running:

+
medperf benchmark ls --mine
+
+

The end of the tutorial

+

Cleanup (Optional)

+

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

+
    +
  • +

    To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

    +
  • +
  • +

    To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

    +
  • +
+
rm -fr medperf_tutorial
+
+
    +
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
  • +
+
cd server
+sh reset_db.sh
+
+
    +
  • To cleanup the test storage:
  • +
+
rm -fr ~/.medperf/localhost_8000
+
+ + + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/data_owner_demo/index.html b/getting_started/data_owner_demo/index.html new file mode 100644 index 000000000..8c1e93cb7 --- /dev/null +++ b/getting_started/data_owner_demo/index.html @@ -0,0 +1,1407 @@ + + + + + + + + + + + + + + + + + + + + + + + + Data Owners - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + +
+
+
+ + + + + + + + + +
+
+ + + + + + + +

Overview

+

Hands-on Tutorial for Data Owners

+

Overview

+

As a data owner, you plan to run a benchmark on your own dataset. Using MedPerf, you will prepare your (raw) dataset and submit information about it to the MedPerf server. You may have to consult the benchmark committee to make sure that your raw dataset aligns with the benchmark's expected input format.

+
+

Note

+

A key concept of MedPerf is the stringent confidentiality of your data. It remains exclusively on your machine. Only minimal information about your dataset, such as the hash of its contents, is submitted. Once your Dataset is submitted and associated with a benchmark, you can run all benchmark models on your data within your own infrastructure and see the results / predictions.

+
+

This guide provides you with the necessary steps to use MedPerf as a Data Owner. The key tasks can be summarized as follows:

+
    +
  1. Register your data information.
  2. +
  3. Prepare your data.
  4. +
  5. Mark your data as operational.
  6. +
  7. Request participation in a benchmark.
  8. +
  9. Execute the benchmark models on your dataset.
  10. +
  11. Submit a result.
  12. +
+

It is assumed that you have the general testing environment set up.

+

Before You Start

+

First steps

+
Running in cloud via Github Codespaces
+

As the most easy way to play with the tutorials you can launch a preinstalled Codespace cloud environment for MedPerf by clicking this link:

+

Open in GitHub Codespaces

+
Running in local environment
+

To start experimenting with MedPerf through this tutorial on your local machine, you need to start by following these quick steps:

+
    +
  1. Install Medperf
  2. +
  3. Set up Medperf
  4. +
+

Prepare the Local MedPerf Server

+

For the purpose of the tutorial, you have to initialize a local MedPerf server with a fresh database and then create the necessary entities that you will be interacting with. To do so, run the following: (make sure you are in MedPerf's root folder)

+
cd server
+sh reset_db.sh
+python seed.py --demo data
+cd ..
+
+

Download the Necessary files

+

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

+
sh tutorials_scripts/setup_data_tutorial.sh
+
+

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. The folder contains the following content:

+
+Toy content description +

Tutorial's Dataset Example

+

The medperf_tutorial/sample_raw_data/ folder contains your data for the specified Benchmark. In this tutorial, where the benchmark involves classifying chest X-Ray images, your data comprises:

+
    +
  • images/ folder contains your images
  • +
  • labels/labels.csv, which provides the ground truth markup, specifying the class of each image.
  • +
+

The format of this data is dictated by the Benchmark Owner, as it must be compatible with the benchmark's Data Preparation MLCube. In a real-world scenario, the expected data format would differ from this toy example. Refer to the Benchmark Owner to get a format specifications and details for your practical case.

+

As previously mentioned, your data itself never leaves your machine. During the dataset submission, only basic metadata is transferred, for which you will be prompted to confirm.

+
+

In real life all the listed artifacts and files have to be created on your own. However, for tutorial's sake you may use this toy data.

+

Login to the Local MedPerf Server

+

The local MedPerf server is pre-configured with a dummy local authentication system. Remember that when you are communicating with the real MedPerf server, you should follow the steps in this guide to login. For the tutorials, you should not do anything.

+

You are now ready to start!

+

1. Register your Data Information

+

Dataset Owner registers the dataset metadata +To register your dataset, you need to collect the following information:

+
    +
  • A name you wish to have for your dataset.
  • +
  • A small description of the dataset.
  • +
  • The source location of your data (e.g., hospital name).
  • +
  • The path to the data records (here, it is medperf_tutorial/sample_raw_data/images).
  • +
  • The path to the labels of the data (here, it is medperf_tutorial/sample_raw_data/labels)
  • +
  • The benchmark ID that you wish to participate in. This ensures your data in the next step will be prepared using the benchmark's data preparation MLCube.
  • +
+
+

Note

+

The data_path and labels_path are determined according to the input path requirements of the data preparation MLCube. To ensure that your data is structured correctly, it is recommended to check with the Benchmark Committee for specific details or instructions.

+
+

In order to find the benchmark ID, you can execute the following command to view the list of available benchmarks.

+
medperf benchmark ls
+
+

The target benchmark ID here is 1.

+
+

Note

+

You will be submitting general information about the data, not the data itself. The data never leaves your machine.

+
+

Run the following command to register your data (make sure you are in MedPerf's root folder):

+
medperf dataset submit \
+  --name "mytestdata" \
+  --description "A tutorial dataset" \
+  --location "My machine" \
+  --data_path "medperf_tutorial/sample_raw_data/images" \
+  --labels_path "medperf_tutorial/sample_raw_data/labels" \
+  --benchmark 1
+
+

Once you run this command, the information to be submitted will be displayed on the screen and you will be asked to confirm your submission. Once you confirm, your dataset will be successfully registered!

+

2. Prepare your Data

+

Dataset Owner prepares the dataset

+

To prepare and preprocess your dataset, you need to know the server UID of your registered dataset. You can check your datasets information by running:

+
medperf dataset ls --mine
+
+

In our tutorial, your dataset ID will be 1. Run the following command to prepare your dataset:

+
medperf dataset prepare --data_uid 1
+
+

This command will also calculate statistics on your data; statistics defined by the benchmark owner. These will be submitted to the MedPerf server in the next step upon your approval.

+

3. Mark your Dataset as Operational

+

Dataset Owner registers the dataset metadata

+

After successfully preparing your dataset, you can mark it as ready so that it can be associated with benchmarks you want. During preparation, your dataset is considered in the Development stage, and now you will mark it as operational.

+
+

Note

+

Once marked as operational, it can never be marked as in-development anymore.

+
+

Run the following command to mark your dataset as operational:

+
medperf dataset set_operational --data_uid 1
+
+

Once you run this command, you will see on your screen the updated information of your dataset along with the statistics mentioned in the previous step. You will be asked to confirm submission of the displayed information. Once you confirm, your dataset will be successfully marked as operational!

+

Next, you can proceed to request participation in the benchmark by initiating an association request.

+

4. Request Participation

+

Dataset Owner requests to participate in the benchmark +For submitting the results of executing the benchmark models on your data in the future, you must associate your data with the benchmark.

+

Once you have submitted your dataset to the MedPerf server, it will be assigned a server UID, which you can find by running medperf dataset ls --mine. Your dataset's server UID is also 1.

+

Run the following command to request associating your dataset with the benchmark:

+
medperf dataset associate --benchmark_uid 1 --data_uid 1
+
+

This command will first run the benchmark's reference model on your dataset to ensure your dataset is compatible with the benchmark workflow. Then, the association request information is printed on the screen, which includes an executive summary of the test mentioned. You will be prompted to confirm sending this information and initiating this association request.

+

How to proceed after requesting association

+

Benchmark Committee accepts / rejects datasets +When participating with a real benchmark, you must wait for the Benchmark Committee to approve the association request. You can check the status of your association requests by running medperf association ls. The association is identified by the server UIDs of your dataset and the benchmark with which you are requesting association.

+

For the sake of continuing the tutorial only, run the following to simulate the benchmark committee approving your association (make sure you are in the MedPerf's root directory):

+
sh tutorials_scripts/simulate_data_association_approval.sh
+
+

You can verify if your association request has been approved by running medperf association ls.

+

5. Execute the Benchmark

+

Dataset Owner runs Benchmark models +MedPerf provides a command that runs all the models of a benchmark effortlessly. You only need to provide two parameters:

+
    +
  • The benchmark ID you want to run, which is 1.
  • +
  • The server UID of your data, which is 1.
  • +
+

For that, run the following command:

+
medperf benchmark run --benchmark 1 --data_uid 1
+
+

After running the command, you will receive a summary of the executions. You will see something similar to the following:

+
  model  local result UID    partial result    from cache    error
+-------  ------------------  ----------------  ------------  -------
+      2  b1m2d1              False             True
+      4  b1m4d1              False             False
+Total number of models: 2
+        1 were skipped (already executed), of which 0 have partial results
+        0 failed
+        1 ran successfully, of which 0 have partial results
+
+✅ Done!
+
+

This means that the benchmark has two models:

+
    +
  • A model that you already ran when you requested the association. This explains why it was skipped.
  • +
  • Another model that ran successfully. Its result generated UID is b1m4d1.
  • +
+

You can view the results by running the following command with the specific local result UID. For example:

+
medperf result view b1m4d1
+
+

For now, your results are only local. Next, you will learn how to submit the results.

+

6. Submit a Result

+

Dataset Owner submits evaluation results +After executing the benchmark, you will submit a result to the MedPerf server. To do so, you have to find the target result generated UID.

+

As an example, you will be submitting the result of UID b1m4d1. To do this, run the following command:

+
medperf result submit --result b1m4d1
+
+

The information that is going to be submitted will be printed to the screen and you will be prompted to confirm that you want to submit.

+

The end

+

Cleanup (Optional)

+

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

+
    +
  • +

    To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

    +
  • +
  • +

    To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

    +
  • +
+
rm -fr medperf_tutorial
+
+
    +
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
  • +
+
cd server
+sh reset_db.sh
+
+
    +
  • To cleanup the test storage:
  • +
+
rm -fr ~/.medperf/localhost_8000
+
+ + + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/installation/index.html b/getting_started/installation/index.html new file mode 100644 index 000000000..775f1fd47 --- /dev/null +++ b/getting_started/installation/index.html @@ -0,0 +1,1057 @@ + + + + + + + + + + + + + + + + + + + + + + + + Installation - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Installation

+

Prerequisites

+

Python

+

Make sure you have Python 3.9 installed along with pip. To check if they are installed, run:

+
python --version
+pip --version
+
+

or, depending on you machine configuration:

+
python3 --version
+pip3 --version
+
+

We will assume the commands' names are pip and python. Use pip3 and python3 if your machine is configured differently.

+

Docker or Singularity

+

Make sure you have the latest version of Docker or Singularity 3.10 installed.

+

To verify docker is installed, run:

+
docker --version
+
+

To verify singularity is installed, run:

+
singularity --version
+
+

If using Docker, make sure you can run Docker as a non-root user.

+

Install MedPerf

+
    +
  1. +

    (Optional) MedPerf is better to be installed in a virtual environment. We recommend using Anaconda. Having anaconda installed, create a virtual environment medperf-env with the following command:

    +
    conda create -n medperf-env python=3.9
    +
    +

    Then, activate your environment:

    +
    conda activate medperf-env
    +
    +
  2. +
  3. +

    Clone the MedPerf repository:

    +
    git clone https://github.com/mlcommons/medperf.git
    +cd medperf
    +
    +
  4. +
  5. +

    Install MedPerf from source:

    +
    pip install -e ./cli
    +
    +
  6. +
  7. +

    Verify the installation:

    +
    medperf --version
    +
    +
  8. +
+

What's Next?

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/model_owner_demo/index.html b/getting_started/model_owner_demo/index.html new file mode 100644 index 000000000..1d16b8836 --- /dev/null +++ b/getting_started/model_owner_demo/index.html @@ -0,0 +1,1363 @@ + + + + + + + + + + + + + + + + + + + + + + + + Model Owners - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + + + +
+
+
+ + + + +
+
+
+ + + + + + + + + +
+
+ + + + + + + +

Overview

+

Hands-on Tutorial for Model Owners

+

Overview

+

In this guide, you will learn how a Model Owner can use MedPerf to take part in a benchmark. It's highly recommend that you follow this or this guide first to implement your own model MLCube and use it throughout this tutorial. However, this guide provides an already implemented MLCube if you want to directly proceed to learn how to interact with MedPerf.

+

The main tasks of this guide are:

+
    +
  1. Testing MLCube compatibility with the benchmark.
  2. +
  3. Submitting the MLCube.
  4. +
  5. Requesting participation in a benchmark.
  6. +
+

It's assumed that you have already set up the general testing environment as explained in the setup guide.

+

Before You Start

+

First steps

+
Running in cloud via Github Codespaces
+

As the most easy way to play with the tutorials you can launch a preinstalled Codespace cloud environment for MedPerf by clicking this link:

+

Open in GitHub Codespaces

+
Running in local environment
+

To start experimenting with MedPerf through this tutorial on your local machine, you need to start by following these quick steps:

+
    +
  1. Install Medperf
  2. +
  3. Set up Medperf
  4. +
+

Prepare the Local MedPerf Server

+

For the purpose of the tutorial, you have to initialize a local MedPerf server with a fresh database and then create the necessary entities that you will be interacting with. To do so, run the following: (make sure you are in MedPerf's root folder)

+
cd server
+sh reset_db.sh
+python seed.py --demo model
+cd ..
+
+

Download the Necessary files

+

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

+
sh tutorials_scripts/setup_model_tutorial.sh
+
+

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. The folder contains the following content:

+
+Toy content description +

Model MLCube

+

The medperf_tutorial/model_mobilenetv2/ is a toy Model MLCube. Once you submit your model to the benchmark, all participating Data Owners would be able to run the model within the benchmark pipeline. Therefore, your MLCube must support the specific input/output formats defined by the Benchmark Owners.

+

For the purposes of this tutorial, you will work with a pre-prepared toy benchmark. In a real-world scenario, you should refer to your Benchmark Owner to get a format specifications and details for your practical case.

+
+

In real life all the listed artifacts and files have to be created on your own. However, for tutorial's sake you may use this toy data.

+

Login to the Local MedPerf Server

+

The local MedPerf server is pre-configured with a dummy local authentication system. Remember that when you are communicating with the real MedPerf server, you should follow the steps in this guide to login. For the tutorials, you should not do anything.

+

You are now ready to start!

+

1. Test your MLCube Compatibility

+

Model Owner implements & tests MLCube +Before submitting your MLCube, it is highly recommended that you test your MLCube compatibility with the benchmarks of interest to avoid later edits and multiple submissions. Your MLCube should be compatible with the benchmark workflow in two main ways:

+
    +
  1. It should expect a specific data input structure
  2. +
  3. Its outputs should follow a particular structure expected by the benchmark's metrics evaluator MLCube
  4. +
+

These details should usually be acquired by contacting the Benchmark Committee and following their instructions.

+

To test your MLCube validity with the benchmark, first run medperf benchmark ls to identify the benchmark's server UID. In this case, it is going to be 1.

+

Next, locate the MLCube. Unless you implemented your own MLCube, the MLCube provided for this tutorial is located in your workspace: medperf_tutorial/model_mobilenetv2/mlcube/mlcube.yaml.

+

After that, run the compatibility test:

+
medperf test run \
+   --benchmark 1 \
+   --model "medperf_tutorial/model_mobilenetv2/mlcube/mlcube.yaml"
+
+

Assuming the test passes successfuly, you are ready to submit the MLCube to the MedPerf server.

+

2. Submit the MLCube

+

Model Owner submits Model MLCube

+

How does MedPerf Recognize an MLCube?

+

The MedPerf server registers an MLCube as metadata comprised of a set of files that can be retrieved from the internet. This means that before submitting an MLCube you have to host its files on the internet. The MedPerf client provides a utility to prepare the files of an MLCube that need to be hosted. You can refer to this page if you want to understand what the files are, but using the utility script is enough.

+

To prepare the files of the MLCube, run the following command ensuring you are in MedPerf's root folder:

+
python scripts/package-mlcube.py --mlcube medperf_tutorial/model_mobilenetv2/mlcube --mlcube-types model
+
+

This script will create a new folder in the MLCube directory, named assets, containing all the files that should be hosted separately.

+

Host the Files

+

For the tutorial to run smoothly, the files are already hosted. If you wish to host them by yourself, you can find the list of supported options and details about hosting files in this page.

+

Submit the MLCube

+

The submission should include the URLs of all the hosted files. For the MLCube provided for the tutorial:

+
    +
  • The URL to the hosted mlcube manifest file is
  • +
+
https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/mlcube.yaml
+
+
    +
  • The URL to the hosted mlcube parameters file is
  • +
+
https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/workspace/parameters.yaml
+
+
    +
  • The URL to the hosted additional files tarball file is
  • +
+
https://storage.googleapis.com/medperf-storage/chestxray_tutorial/mobilenetv2_weights.tar.gz
+
+

Use the following command to submit:

+
medperf mlcube submit \
+   --name my-model-cube \
+   --mlcube-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/mlcube.yaml" \
+   --parameters-file "https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/workspace/parameters.yaml" \
+   --additional-file "https://storage.googleapis.com/medperf-storage/chestxray_tutorial/mobilenetv2_weights.tar.gz" \
+   --operational
+
+

The MLCube will be assigned by a server UID. You can check it by running:

+
medperf mlcube ls --mine
+
+

3. Request Participation

+

Model Owner requests to participate in the benchmark +Benchmark workflows are run by Data Owners, who will get notified when a new model is added to a benchmark. You must request the association for your model to be part of the benchmark.

+

To initiate an association request, you need to collect the following information:

+
    +
  • The target benchmark ID, which is 1
  • +
  • The server UID of your MLCube, which is 4.
  • +
+

Run the following command to request associating your MLCube with the benchmark:

+
medperf mlcube associate --benchmark 1 --model_uid 4
+
+

This command will first run the benchmark's workflow on your model to ensure your model is compatible with the benchmark workflow. Then, the association request information is printed on the screen, which includes an executive summary of the test mentioned. You will be prompted to confirm sending this information and initiating this association request.

+

What Happens After Requesting the Association?

+

Benchmark Committee accepts / rejects models +When participating with a real benchmark, you must wait for the Benchmark Committee to approve the association request. You can check the status of your association requests by running medperf association ls. The association is identified by the server UIDs of your MLCube and the benchmark with which you are requesting association.

+

The end

+

Cleanup (Optional)

+

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

+
    +
  • +

    To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

    +
  • +
  • +

    To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

    +
  • +
+
rm -fr medperf_tutorial
+
+
    +
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
  • +
+
cd server
+sh reset_db.sh
+
+
    +
  • To cleanup the test storage:
  • +
+
rm -fr ~/.medperf/localhost_8000
+
+ + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/overview/index.html b/getting_started/overview/index.html new file mode 100644 index 000000000..6857610c4 --- /dev/null +++ b/getting_started/overview/index.html @@ -0,0 +1,887 @@ + + + + + + + + + + + + + + + + + + + + Overview - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Overview

+

The MedPerf client provides all the necessary tools to run a complete benchmark experiment. Below, you will find a comprehensive breakdown of user roles and the corresponding functionalities they can access and perform using the MedPerf client:

+
    +
  • Benchmark Committee: The Benchmark Commitee can define and create a benchmark, as well as manage experiments (e.g., approving which datasets and models will be allowed to participate)
  • +
  • Model Owner: The Model Owner can submit a model to the MedPerf server and request participation in a benchmark.
  • +
  • Data Owner: The Data Owner can prepare their raw medical data, register the metadata of their prepared dataset, request participation in a benchmark, execute a benchmark's models on their data, and submit the results of the execution.
  • +
+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/setup/index.html b/getting_started/setup/index.html new file mode 100644 index 000000000..e58aa0554 --- /dev/null +++ b/getting_started/setup/index.html @@ -0,0 +1,1058 @@ + + + + + + + + + + + + + + + + + + + + + + + + Setup - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Setup

+

This setup is only for running the tutorials. If you are using MedPerf with a real benchmark and real experiments, skip to this section to optionally change your container runner. Then, follow the tutorials as a general guidance for your real experiments.

+

Install the MedPerf Client

+

If this is your first time using MedPerf, install the MedPerf client library as described here.

+

Run a Local MedPerf Server

+

For this tutorial, you should spawn a local MedPerf server for the MedPerf client to communicate with. Note that this server will be hosted on your localhost and not on the internet.

+
    +
  1. +

    Install the server requirements ensuring you are in MedPerf's root folder:

    +
    pip install -r server/requirements.txt
    +pip install -r server/test-requirements.txt
    +
    +
  2. +
  3. +

    Run the local MedPerf server using the following command:

    +
    cd server
    +cp .env.local.local-auth .env
    +sh setup-dev-server.sh
    +
    +
  4. +
+

The local MedPerf server now is ready to recieve requests. You can always stop the server by pressing CTRL+C in the terminal where you ran the server.

+

After that, you will be configuring the MedPerf client to communicate with the local MedPerf server. Make sure you continue following the instructions in a new terminal.

+

Configure the MedPerf Client

+ +

The MedPerf client can be configured by creating or modifying "profiles". A profile is a set of configuration parameters used by the client during runtime. By default, the profile named default will be active.

+

The default profile is preconfigured so that the client communicates with the main MedPerf server (api.medperf.org). For the purposes of the tutorial, you will be using the local profile as it is preconfigured so that the client communicates with the local MedPerf server.

+

To activate the local profile, run the following command:

+
medperf profile activate local
+
+

You can always check which profile is active by running:

+
medperf profile ls
+
+

To view the current active profile's configured parameters, you can run the following:

+
medperf profile view
+
+

Choose the Container Runner

+

You can configure the MedPerf client to use either Docker or Singularity. The local profile is configured to use Docker. If you want to use MedPerf with Singularity, modify the local profile configured parameters by running the following:

+
medperf profile set --platform singularity
+
+

This command will modify the platform parameter of the currently activated profile.

+

What's Next?

+

The local MedPerf server now is ready to recieve requests, and the MedPerf client is ready to communicate. Depending on your role, you can follow these hands-on tutorials:

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/shared/before_we_start/index.html b/getting_started/shared/before_we_start/index.html new file mode 100644 index 000000000..72d7f2785 --- /dev/null +++ b/getting_started/shared/before_we_start/index.html @@ -0,0 +1,887 @@ + + + + + + + + + + + + + + + + + + + + Before we start - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Macro Rendering Error

+

File: getting_started/shared/before_we_start.md

+

UndefinedError: 'dict object' has no attribute 'tutorial_id'

+
Traceback (most recent call last):
+  File "/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/mkdocs_macros/plugin.py", line 527, in render
+    return md_template.render(**page_variables)
+  File "/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
+    self.environment.handle_exception()
+  File "/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
+    raise rewrite_traceback_stack(source=source)
+  File "<template>", line 41, in top-level template code
+jinja2.exceptions.UndefinedError: 'dict object' has no attribute 'tutorial_id'
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/shared/cleanup/index.html b/getting_started/shared/cleanup/index.html new file mode 100644 index 000000000..4c3d39940 --- /dev/null +++ b/getting_started/shared/cleanup/index.html @@ -0,0 +1,912 @@ + + + + + + + + + + + + + + + + + + + + Cleanup - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Cleanup

+ +

Cleanup (Optional)

+

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

+
    +
  • +

    To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

    +
  • +
  • +

    To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

    +
  • +
+
rm -fr medperf_tutorial
+
+
    +
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
  • +
+
cd server
+sh reset_db.sh
+
+
    +
  • To cleanup the test storage:
  • +
+
rm -fr ~/.medperf/localhost_8000
+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/shared/mlcube_submission_overview/index.html b/getting_started/shared/mlcube_submission_overview/index.html new file mode 100644 index 000000000..ce22b3b24 --- /dev/null +++ b/getting_started/shared/mlcube_submission_overview/index.html @@ -0,0 +1,870 @@ + + + + + + + + + + + + + + + + + + + + Mlcube submission overview - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Mlcube submission overview

+ +

The MedPerf server registers an MLCube as metadata comprised of a set of files that can be retrieved from the internet. This means that before submitting an MLCube you have to host its files on the internet. The MedPerf client provides a utility to prepare the files of an MLCube that need to be hosted. You can refer to this page if you want to understand what the files are, but using the utility script is enough.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/shared/redirect_to_hosting_files/index.html b/getting_started/shared/redirect_to_hosting_files/index.html new file mode 100644 index 000000000..43ce25577 --- /dev/null +++ b/getting_started/shared/redirect_to_hosting_files/index.html @@ -0,0 +1,891 @@ + + + + + + + + + + + + + + + + + + + + Redirect to hosting files - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Redirect to hosting files

+ +

Host the Files

+

For the tutorial to run smoothly, the files are already hosted. If you wish to host them by yourself, you can find the list of supported options and details about hosting files in this page.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/shared/tutorials_content_overview/benchmark/index.html b/getting_started/shared/tutorials_content_overview/benchmark/index.html new file mode 100644 index 000000000..b8a11163a --- /dev/null +++ b/getting_started/shared/tutorials_content_overview/benchmark/index.html @@ -0,0 +1,926 @@ + + + + + + + + + + + + + + + + + + + + Benchmark - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Benchmark

+ +

In this tutorial we will create a benchmark that classifies chest X-Ray images.

+

Demo Data

+

The medperf_tutorial/demo_data/ folder contains the demo dataset content.

+
    +
  • images/ folder includes sample images.
  • +
  • labels/labels.csv provides a basic ground truth markup, indicating the class each image belongs to.
  • +
+

The demo dataset is a sample dataset used for the development of your benchmark and used by Model Owners for the development of their models. More details are available in the section below

+

Data Preparator MLCube

+

The medperf_tutorial/data_preparator/ contains a DataPreparator MLCube that you must implement. This MLCube: + - Transforms raw data into a format convenient for model consumption, such as converting DICOM images into numpy tensors, cropping patches, normalizing columns, etc. It's up to you to define the format that is handy for future models. + - Ensures its output is in a standardized format, allowing Model Owners/Developers to rely on its consistency.

+

Model MLCube

+

The medperf_tutorial/model_custom_cnn/ is an example of a Model MLCube. You need to implement a reference model which will be used by data owners to test the compatibility of their data with your pipeline. Also, Model Developers joining your benchmark will follow the input/output specifications of this model when building their own models.

+

Metrics MLCube

+

The medperf_tutorial/metrics/ houses a Metrics MLCube that processes ground truth data, model predictions, and computes performance metrics - such as classification accuracy, loss, etc. After a Dataset Owner runs the benchmark pipeline on their data, these final metric values will be shared with you as the Benchmark Owner.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/shared/tutorials_content_overview/data/index.html b/getting_started/shared/tutorials_content_overview/data/index.html new file mode 100644 index 000000000..10d677bad --- /dev/null +++ b/getting_started/shared/tutorials_content_overview/data/index.html @@ -0,0 +1,897 @@ + + + + + + + + + + + + + + + + + + + + Data - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Data

+ +

Tutorial's Dataset Example

+

The medperf_tutorial/sample_raw_data/ folder contains your data for the specified Benchmark. In this tutorial, where the benchmark involves classifying chest X-Ray images, your data comprises:

+
    +
  • images/ folder contains your images
  • +
  • labels/labels.csv, which provides the ground truth markup, specifying the class of each image.
  • +
+

The format of this data is dictated by the Benchmark Owner, as it must be compatible with the benchmark's Data Preparation MLCube. In a real-world scenario, the expected data format would differ from this toy example. Refer to the Benchmark Owner to get a format specifications and details for your practical case.

+

As previously mentioned, your data itself never leaves your machine. During the dataset submission, only basic metadata is transferred, for which you will be prompted to confirm.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/shared/tutorials_content_overview/model/index.html b/getting_started/shared/tutorials_content_overview/model/index.html new file mode 100644 index 000000000..5e8e7de47 --- /dev/null +++ b/getting_started/shared/tutorials_content_overview/model/index.html @@ -0,0 +1,892 @@ + + + + + + + + + + + + + + + + + + + + Model - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Model

+ +

Model MLCube

+

The medperf_tutorial/model_mobilenetv2/ is a toy Model MLCube. Once you submit your model to the benchmark, all participating Data Owners would be able to run the model within the benchmark pipeline. Therefore, your MLCube must support the specific input/output formats defined by the Benchmark Owners.

+

For the purposes of this tutorial, you will work with a pre-prepared toy benchmark. In a real-world scenario, you should refer to your Benchmark Owner to get a format specifications and details for your practical case.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/signup/index.html b/getting_started/signup/index.html new file mode 100644 index 000000000..36369b92d --- /dev/null +++ b/getting_started/signup/index.html @@ -0,0 +1,947 @@ + + + + + + + + + + + + + + + + + + + + + + + + Create an Account - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Create your MedPerf Account

+

MedPerf uses passwordless authentication. This means that there will be no need for a password, and you have to access your email in order complete the signup process.

+

Automatic signups are currently disabled. Please contact the MedPerf team in order to provision an account.

+
+

Tip

+

You don't need an account to run the tutorials and learn how to use the MedPerf client.

+
+

What's Next?

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/getting_started/tutorials_overview/index.html b/getting_started/tutorials_overview/index.html new file mode 100644 index 000000000..bc641d882 --- /dev/null +++ b/getting_started/tutorials_overview/index.html @@ -0,0 +1,923 @@ + + + + + + + + + + + + + + + + + + + + + + + + Introduction - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Overview

+

The tutorials simulate a benchmarking example for the task of detecting thoracic diseases from chest X-ray scans. You can find the description of the used data here. Throughout the tutorials, you will be interacting with a temporary local MedPerf server as described in the setup page. This allows you to freely experiment with the MedPerf client and rerun the tutorials as many times as you want, providing you with an immersive learning experience. Please note that these tutorials also serve as a general guidance to be followed when using the MedPerf client in a real scenario.

+

Before proceeding to the tutorials, make sure you have the general tutorial environment set up.

+

To ensure users have the best experience in learning the fundamentals of MedPerf and utilizing the MedPerf client, the following set of tutorials are provided:

+ + + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/images/benchmark_champions.png b/images/benchmark_champions.png new file mode 100644 index 000000000..0f80133fd Binary files /dev/null and b/images/benchmark_champions.png differ diff --git a/images/benchmark_committee.gif b/images/benchmark_committee.gif new file mode 100644 index 000000000..e44de890e Binary files /dev/null and b/images/benchmark_committee.gif differ diff --git a/images/benchmark_committee.png b/images/benchmark_committee.png new file mode 100644 index 000000000..52302c06c Binary files /dev/null and b/images/benchmark_committee.png differ diff --git a/images/benefits.png b/images/benefits.png new file mode 100644 index 000000000..5fcdf849b Binary files /dev/null and b/images/benefits.png differ diff --git a/images/data_owners.png b/images/data_owners.png new file mode 100644 index 000000000..d8ef843f1 Binary files /dev/null and b/images/data_owners.png differ diff --git a/images/dataset_preparation_association.png b/images/dataset_preparation_association.png new file mode 100644 index 000000000..7970d22e3 Binary files /dev/null and b/images/dataset_preparation_association.png differ diff --git a/images/execution_flow_folders.PNG b/images/execution_flow_folders.PNG new file mode 100644 index 000000000..ee276e4e7 Binary files /dev/null and b/images/execution_flow_folders.PNG differ diff --git a/images/fed_eva_example.gif b/images/fed_eva_example.gif new file mode 100644 index 000000000..88a490ebc Binary files /dev/null and b/images/fed_eva_example.gif differ diff --git a/images/flow_preparation_association_folders.PNG b/images/flow_preparation_association_folders.PNG new file mode 100644 index 000000000..bc6ee0f3a Binary files /dev/null and b/images/flow_preparation_association_folders.PNG differ diff --git a/images/full_diagram.PNG b/images/full_diagram.PNG new file mode 100644 index 000000000..221c27146 Binary files /dev/null and b/images/full_diagram.PNG differ diff --git a/images/homepage/logo.png b/images/homepage/logo.png new file mode 100644 index 000000000..478481561 Binary files /dev/null and b/images/homepage/logo.png differ diff --git a/images/homepage/logo.svg b/images/homepage/logo.svg new file mode 100644 index 000000000..6de426663 --- /dev/null +++ b/images/homepage/logo.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + diff --git a/images/model_owners.png b/images/model_owners.png new file mode 100644 index 000000000..28b050093 Binary files /dev/null and b/images/model_owners.png differ diff --git a/images/platform provider.png b/images/platform provider.png new file mode 100644 index 000000000..822abd63f Binary files /dev/null and b/images/platform provider.png differ diff --git a/images/submitting_associating_additional_models_1.png b/images/submitting_associating_additional_models_1.png new file mode 100644 index 000000000..be135ba80 Binary files /dev/null and b/images/submitting_associating_additional_models_1.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..cd1136c71 --- /dev/null +++ b/index.html @@ -0,0 +1,1158 @@ + + + + + + + + + + + + + + + + + + + + + + Title - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + + + + + +
+
+
+
+ +
+

Explore our documentation hub to understand everything you need to get started with MedPerf, + including definitions, setup, tutorials, advanced concepts, and more.

+ +
+
+
+
+
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +
+
+ + +
+ +
+ +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/medperf_components/index.html b/medperf_components/index.html new file mode 100644 index 000000000..ba7294a88 --- /dev/null +++ b/medperf_components/index.html @@ -0,0 +1,974 @@ + + + + + + + + + + + + + + + + + + + + + + + + Components - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

MedPerf Components

+

architecture

+

MedPerf Server

+

The server contains all the metadata necessary to coordinate and execute experiments. No code assets or datasets are stored on the server.

+

The backend server is implemented in Django, and it can be found in the server folder in the MedPerf Github repository.

+

MedPerf Client

+

The MedPerf client contains all the necessary tools to interact with the server, preparing datasets for benchmarks and running experiments on the local machine. It can be found in this folder in the MedPerf Github repository.

+

The client communicates to the server through the API to, for example, authenticate a user, retrieve benchmarks/MLcubes and send results.

+

The client is currently available to the user through a command-line interface (CLI).

+

Auth Provider

+

The auth provider manages MedPerf users identities, authentication, and authorization to access the MedPerf server. Users will authenticate with the auth provider and authorize their MedPerf client to access the MedPerf server. Upon authorization, the MedPerf client will use access tokens issued by the auth provider in every request to the MedPerf server. The MedPerf server is configured to processes only requests authorized by the auth provider.

+

Currently, MedPerf uses Auth0 as the auth provider.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/gandlf_mlcube/index.html b/mlcubes/gandlf_mlcube/index.html new file mode 100644 index 000000000..65ce6cfa7 --- /dev/null +++ b/mlcubes/gandlf_mlcube/index.html @@ -0,0 +1,1176 @@ + + + + + + + + + + + + + + + + + + + + + + + + Creating Model MLCubes from GaNDLF - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Creating a GaNDLF MLCube

+

Overview

+

This guide will walk you through how to wrap a model trained using GaNDLF as a MedPerf-compatible MLCube ready to be used for inference (i.e. as a Model MLCube). The steps can be summarized as follows:

+
    +
  1. Train a GaNDLF model
  2. +
  3. Create the MLCube file
  4. +
  5. (Optional) Create a custom entrypoint
  6. +
  7. Deploy the GaNDLF model as an MLCube
  8. +
+

Before proceeding, make sure you have medperf installed and GaNDLF installed.

+

Before We Start

+

Download the Necessary files

+

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

+
sh tutorials_scripts/setup_GaNDLF_mlcube_tutorial.sh
+
+

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. Run cd medperf_tutorial to switch to this folder.

+

1. Train a GaNDLF Model

+

Train a small GaNDLF model to use for this guide. You can skip this step if you already have a trained model.

+

Make sure you are in the workspace folder medperf_tutorial. Run:

+
gandlf_run \
+  -c ./config_getting_started_segmentation_rad3d.yaml \
+  -i ./data.csv \
+  -m ./trained_model_output \
+  -t True \
+  -d cpu
+
+

Note that if you want to train on GPU you can use -d cuda, but the example used here should take only few seconds using the CPU.

+
+

Warning

+

This tutorial assumes the user is using the latest GaNDLF version. The configuration file config_getting_started_segmentation_rad3d.yaml will cause problems if you are using a different version, make sure you do the necessary changes.

+
+

You will now have your trained model and its related files in the folder trained_model_output. Next, you will start learning how to wrap this trained model within an MLCube.

+

2. Create the MLCube File

+

MedPerf provides a cookiecutter to create an MLCube file that is ready to be consumed by gandlf_deploy and produces an MLCube ready to be used by MedPerf. To create the MLCube, run: (make sure you are in the workspace folder medperf_tutorial)

+
medperf mlcube create gandlf
+
+
+

Note

+

MedPerf is running CookieCutter under the hood. This medperf command provides additional arguments for handling different scenarios. You can see more information on this by running medperf mlcube create --help

+
+

You will be prompted to customize the MLCube creation. Below is an example of how your response might look like:

+
project_name [GaNDLF MLCube]: My GaNDLF MLCube # (1)!
+project_slug [my_gandlf_mlcube]: my_gandlf_mlcube # (2)!
+description [GaNDLF MLCube Template. Provided by MLCommons]: GaNDLF MLCube implementation # (3)!
+author_name [John Smith]: John Smith # (4)!
+accelerator_count [1]: 0 # (5)!
+docker_build_file [Dockerfile-CUDA11.6]: Dockerfile-CPU # (6)!
+docker_image_name [docker/image:latest]: johnsmith/gandlf_model:0.0.1 # (7)!
+
+
    +
  1. Gives a Human-readable name to the MLCube Project.
  2. +
  3. Determines how the MLCube root folder will be named.
  4. +
  5. Gives a Human-readable description to the MLCube Project.
  6. +
  7. Documents the MLCube implementation by specifying the author. Please use your own name here.
  8. +
  9. Indicates how many GPUs should be visible by the MLCube.
  10. +
  11. Indicates the Dockerfile name from GaNDLF that should be used for building your docker image. Use the name of the Dockerfile that aligns with your model's dependencies. Any "Dockerfile-*" in the GaNDLF source repository is valid.
  12. +
  13. MLCubes use containers under the hood. Medperf supports both Docker and Singularity. Here, you can provide an image tag to the image that will be created by this MLCube. It's recommended to use a naming convention that allows you to upload it to Docker Hub.
  14. +
+

Assuming you chose my_gandlf_mlcube as the project slug, you will find your MLCube created under the folder my_gandlf_mlcube. Next, you will use a GaNDLF utility to build the MLCube.

+
+

Note

+

You might need to specify additional configurations in the mlcube.yaml file if you are using a GPU. Check the generated mlcube.yaml file for more info, as well as the MLCube documentation.

+
+

3. (Optional) Create a Custom Entrypoint

+

When deploying the GaNDLF model directly as a model MLCube, the default entrypoint will be gandlf_run .... You can override the entrypoint with a custom python script. One of the usecases is described below.

+

gandlf_run expects a data.csv file in the input data folder, which describes the inference test cases and their associated paths (Read more about GaNDLF's csv file conventions here). In case your MLCube will expect a data folder with a predefined data input structure but without this csv file, you can use a custom script that prepares this csv file as an entrypoint. You can find the recommended template and an example here.

+

4. Deploy the GaNDLF Model as an MLCube

+

To deploy the GaNDLF model as an MLCube, run the following: (make sure you are in the workspace folder medperf_tutorial)

+
gandlf_deploy \
+  -c ./config_getting_started_segmentation_rad3d.yaml \
+  -m ./trained_model_output \
+  --target docker \
+  --mlcube-root ./my_gandlf_mlcube \
+  -o ./built_gandlf_mlcube \
+  --mlcube-type model \
+  --entrypoint <(optional) path to your custom entrypoint script> \ # (1)!
+  -g False # (2)!
+
+
    +
  1. If you are not using a custom entrypoint, ignore this option.
  2. +
  3. Change to True if you want the resulting MLCube to use a GPU for inference.
  4. +
+

GaNDLF will use your initial MLCube configuration my_gandlf_mlcube, the GaNDLF experiment configuration file config_classification.yaml, and the trained model trained_model_output to create a ready MLCube built_gandlf_mlcube and build the docker image that will be used by the MLCube. The docker image will have the model weights and the GaNDLF experiment configuration file embedded. You can check that your image was built by running docker image ls. You will see johnsmith/gandlf_model:0.0.1 (or whatever image name that was used) created moments ago.

+

5. Next Steps

+

That's it! You have built a MedPerf-compatible MLCube with GaNDLF. You may want to submit your MLCube to MedPerf, you can follow this tutorial.

+
+

Tip

+

MLCubes created by GaNDLF have the model weights and configuration file embedded in the docker image. When you want to deploy your MLCube for MedPerf, all you need to do is pushing the docker image and hosting the mlcube.yaml file.

+
+

Cleanup (Optional)

+

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

+
    +
  • To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):
  • +
+
rm -fr medperf_tutorial
+
+

See Also

+ + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/mlcube_data/index.html b/mlcubes/mlcube_data/index.html new file mode 100644 index 000000000..28f85fc8e --- /dev/null +++ b/mlcubes/mlcube_data/index.html @@ -0,0 +1,893 @@ + + + + + + + + + + + + + + + + + + + + + + + + Creating Data Preparator MLCubes from Scratch - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

In Progress

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/mlcube_data_WIP/index.html b/mlcubes/mlcube_data_WIP/index.html new file mode 100644 index 000000000..4b26295bd --- /dev/null +++ b/mlcubes/mlcube_data_WIP/index.html @@ -0,0 +1,1280 @@ + + + + + + + + + + + + + + + + + + + + mlcube data WIP - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

TODO: Change the structure to align with mlcube_models, to help users wrap their existing code into mlcube

+

Data Preparator MLCube

+

Introduction

+

This guide is one of three designed to assist users in building MedPerf-compatible MLCubes. The other two guides focus on creating a Model MLCube and a Metrics MLCube. Together, these three MLCubes form a complete benchmark workflow for the task of thoracic disease detection from Chest X-rays.

+

In summary, a functional MedPerf pipeline includes these steps:

+
    +
  1. The Data Owner exports a raw dataset from their databases (manually or via scripts) - this step occurs outside the pipeline itself. Let's name the output folder as my_raw_data/. If the pipeline is run by another person (Model Owner/Benchmark Owner), a predefined my_benchmark_demo_raw_data/ would be used instead (created and distributed by the Benchmark Owner).
  2. +
  3. The Data Preparator MLCube takes this folder's path as input and converts data into a standardized format, resulting in some my_prepared_dataset/ folder (MLCube is implemented by the Benchmark Owner).
  4. +
  5. The Model MLCube processes the prepared data, running a model and saving the results in some my_model_predictions/ folder (MLCube is implemented by the Model Owner; the Benchmark Owner must implement a baseline model MLCube to be used as a mock-up).
  6. +
  7. The Metrics MLCube processes predictions and evaluates metrics, saving them in some my_metrics.yaml file (MLCube implemented by the Benchmark Owner).
  8. +
  9. The Data Owner reviews the metric results and may submit them to the MedPerf server.
  10. +
+

Aforementioned guides detail steps 2-4. As all steps demonstrate building specific MLCubes, we recommend starting with the Model MLCube guide, which offers a more detailed explanation of the MLCube's concept and structure. Another option is to explore MLCube basic docs. In this guide provides the shortened concepts description, focusing on nuances and input/output parameters.

+

About this Guide

+

This guide describes the tasks, structure and input/output parameters of Data Preparator MLCube, allowing users at the end to be able to implement their own MedPerf-compatible MLCube for Benchmark purposes.

+

The guide starts with general advices, steps, and the required API for building these MLCubes. Subsequently, it will lead you through creating your MLCube using the Chest X-ray Data Preprocessor MLCube as a practical example.

+

It's considered best practice to handle data in various formats. For instance, if the benchmark involves image processing, it's beneficial to support JPEGs, PNGs, BMPs, and other expected image formats; accommodate large and small images, etc. Such flexibility simplifies the process for Dataset Owners, allowing them to export data in their preferred format. The Data Preparator's role is to convert all reasonable input data into a unified format.

+

Before Building the MLCube

+

Your MLCube must implement three command tasks:

+
    +
  • prepare: your main task that transforms raw input data into a unified format.
  • +
  • sanity_check: verifies the cleanliness and consistency of prepare outputs (e.g., ensuring no records lack ground truth labels, labels contain only expected values, data fields are reasonable without outliers or NaNs, etc.)
  • +
  • statistics: Calculates some aggregated statistics on the transformed dataset. Once the Dataset Owner submits their dataset, these statistics will be uploaded to you as the Benchmark Owner.
  • +
+

It's assumed that you already have:

+
    +
  • Some raw data.
  • +
  • The expected unified format for data conversion.
  • +
  • A working implementation of all three tasks.
  • +
+

This guide will help you encapsulate your preparation code within an MLCube. Make sure you extracted each part of your logic, so it can be run independently.

+

Required API

+

Each command execution receives specific parameters. While you are flexible in code implementation, keep in mind that your implementation will receive the following input arguments:

+

Data Preparation API (prepare Command)

+

The parameters include: +- data_path: the path to the raw data folder (read-only). +- labels_path: the path to the ground truth labels folder (read-only). +- Any other optional extra params that you attach to the MLCube, such as path to .txt file with acceptable labels. Note: these extra parameters contain values defined by you, the MLCube owner, not the users' data. +- output_path: an r/w folder for storing transformed dataset objects. +- output_labels_path: an r/w folder for storing transformed labels.

+

Sanity Check API (sanity_check Command)

+

The parameters include: +- data_path: the path to the transformed data folder (read-only). +- labels_path: the path to the transformed ground truth labels folder (read-only). +- Any other optional extra params that you attach to the MLCube - same as for the prepare command.

+

The sanity check does not produce outputs; it either completes successfully or fails.

+

Statistics API (statistics Command)

+
    +
  • data_path: the path to the transformed data folder (read-only).
  • +
  • labels_path: the path to the transformed ground truth labels folder (read-only).
  • +
  • Any other optional extra params that you attach to the MLCube - same as for prepare command.
  • +
  • output_path: path to .yaml file where your code should write down calculated statistics.
  • +
+

Build Your Own MLCube

+

While this guide leads you through creating your own MLCube, you can always check a prebuilt example for a better understanding of how it works in an already implemented MLCube. The example is available here: +

cd examples/chestxray_tutorial/data_preparator/
+

+

The guide uses this implementation to describe concepts.

+

Use an MLCube Template

+

First, ensure you have MedPerf installed. Create a Data Preparator MLCube template by running the following command:

+
medperf mlcube create data_preparator
+
+

You will be prompted to fill in some configuration options through the CLI. Below are the options and their default values:

+
project_name [Data Preparator MLCube]: # (1)!
+project_slug [data_preparator_mlcube]: # (2)!
+description [Data Preparator MLCube Template. Provided by MLCommons]: # (3)!
+author_name [John Smith]: # (4)!
+accelerator_count [0]: # (5)!
+docker_image_name [docker/image:latest]: # (6)!
+
+
    +
  1. Gives a Human-readable name to the MLCube Project.
  2. +
  3. Determines how the MLCube root folder will be named.
  4. +
  5. Gives a Human-readable description to the MLCube Project.
  6. +
  7. Documents the MLCube implementation by specifying the author.
  8. +
  9. Specifies how many GPUs should be visible by the MLCube.
  10. +
  11. MLCubes use Docker containers under the hood. Here, you can provide an image tag for the image created by this MLCube. You should use a valid name that allows you to upload it to a Docker registry.
  12. +
+

After filling the configuration options, the following directory structure will be generated:

+
.
+└── evaluator_mlcube
+    ├── mlcube
+       ├── mlcube.yaml
+       └── workspace
+           └── parameters.yaml
+    └── project
+        ├── Dockerfile
+        ├── mlcube.py
+        └── requirements.txt
+
+

The project Folder

+

This is where your preprocessing logic will live. It contains a standard Docker image project with a specific API for the entrypoint. mlcube.py contains the entrypoint and handles all the tasks we've described. Update this template with your code and bind your logic to specified functions for all three commands. +Refer to the Chest X-ray tutorial example for an example of how it should look:

+
mlcube.py
"""MLCube handler file"""
+import typer
+import yaml
+from prepare import prepare_dataset
+from sanity_check import perform_sanity_checks
+from stats import generate_statistics
+
+app = typer.Typer()
+
+
+@app.command("prepare")
+def prepare(
+    data_path: str = typer.Option(..., "--data_path"),
+    labels_path: str = typer.Option(..., "--labels_path"),
+    parameters_file: str = typer.Option(..., "--parameters_file"),
+    output_path: str = typer.Option(..., "--output_path"),
+    output_labels_path: str = typer.Option(..., "--output_labels_path"),
+):
+    with open(parameters_file) as f:
+        parameters = yaml.safe_load(f)
+
+    prepare_dataset(data_path, labels_path, parameters, output_path, output_labels_path)
+
+
+@app.command("sanity_check")
+def sanity_check(
+    data_path: str = typer.Option(..., "--data_path"),
+    labels_path: str = typer.Option(..., "--labels_path"),
+    parameters_file: str = typer.Option(..., "--parameters_file"),
+):
+    with open(parameters_file) as f:
+        parameters = yaml.safe_load(f)
+
+    perform_sanity_checks(data_path, labels_path, parameters)
+
+
+@app.command("statistics")
+def statistics(
+    data_path: str = typer.Option(..., "--data_path"),
+    labels_path: str = typer.Option(..., "--labels_path"),
+    parameters_file: str = typer.Option(..., "--parameters_file"),
+    out_path: str = typer.Option(..., "--output_path"),
+):
+    with open(parameters_file) as f:
+        parameters = yaml.safe_load(f)
+
+    generate_statistics(data_path, labels_path, parameters, out_path)
+
+
+if __name__ == "__main__":
+    app()
+
+

The mlcube Folder

+

This folder is primarily for configuring your MLCube and providing additional files the MLCube may interact with, such as parameters or model weights.

+

mlcube.yaml MLCube Configuration

+

The mlcube/mlcube.yaml file contains metadata and configuration of your mlcube. This file is already populated with the configuration you provided during the template creation step. There is no need to edit anything in this file except if you are specifying extra parameters to the commands (e.g., you want to pass a sklearn's StardardScaler weights or any other parameters required for data transformation).

+
mlcube.py
name: Chest X-ray Data Preparator
+description: MedPerf Tutorial - Data Preparation MLCube.
+authors:
+  - { name: MLCommons Medical Working Group }
+
+platform:
+  accelerator_count: 0
+
+docker:
+  # Image name
+  image: mlcommons/chestxray-tutorial-prep:0.0.0
+  # Docker build context relative to $MLCUBE_ROOT. Default is `build`.
+  build_context: "../project"
+  # Docker file name within docker build context, default is `Dockerfile`.
+  build_file: "Dockerfile"
+
+tasks:
+  prepare:
+    parameters:
+      inputs:
+        {
+          data_path: input_data,
+          labels_path: input_labels,
+          parameters_file: parameters.yaml,
+        }
+      outputs: { output_path: data/, output_labels_path: labels/ }
+  sanity_check:
+    parameters:
+      inputs:
+        {
+          data_path: data/,
+          labels_path: labels/,
+
+          parameters_file: parameters.yaml,
+        }
+  statistics:
+    parameters:
+      inputs:
+        {
+          data_path: data/,
+          labels_path: labels/,
+
+          parameters_file: parameters.yaml,
+        }
+      outputs: { output_path: { type: file, default: statistics.yaml } }
+
+

All paths are relative to mlcube/workspace/ folder.

+

To set up additional inputs, add a key-value pair in the task's inputs dictionary:

+
mlcube.yaml
...
+  prepare:
+    parameters:
+      inputs:
+        {
+          data_path: input_data,
+          labels_path: input_labels,
+          parameters_file: parameters.yaml,
+          standardscaler_weights: additional_files/standardscaler.pkl
+        }
+      outputs: { output_path: data/, output_labels_path: labels/ }
+...
+
+

Considering the note about path locations, this new file should be stored at mlcube/workspace/additional_files/standardscaler.pkl

+

Parameters

+

Your preprocessing logic might depend on certain parameters (e.g., which labels are accepted). It is generally better to pass such parameters when running the MLCube, rather than hardcoding them. This can be done via a parameters.yaml file that is passed to the MLCube. This file will be available to the previously described commands (if you declare it in the inputs dict of a specific command). You can parse this file in the mlcube.py file and pass its contents to your logic.

+

This file should be placed in the mlcube/workspace folder.

+

Build Your MLCube

+

After you follow the previous sections and fulfill the image with your logic, the MLCube is ready to be built and run. Run the command below to build the MLCube. Ensure you are in the mlcube/ subfolder of your Data Preparator.

+
mlcube configure -Pdocker.build_strategy=always
+
+

This command builds your Docker image and prepares the MLCube for use.

+

Run Your MLCube

+

MedPerf will take care of running your MLCube. However, it's recommended to test the MLCube alone before using it with MedPerf for better debugging.

+

To run the MLCube, use the command below. Ensure you are located in the mlcube/ subfolder of your Data Preparator.

+
mlcube run --task prepare data_path=<path_to_raw_data> \
+  labels_path=<path_to_raw_labels> \
+  output_path=<path_to_save_transformed_data> \
+  output_labels_path=<path_to_save_transformed_labels>
+
+
+

Relative paths

+

Keep in mind that though we are running tasks from mlcube/, all the paths should be absolute or relative to mlcube/workspace/.

+
+
+

Default values

+

We have declared a default values for every path parameter. This allows for omitting these parameters in our commands.

+

Consider the following structure: +

.
+└── data_preparator_mlcube
+    ├── mlcube
+       ├── mlcube.yaml
+       └── workspace
+           └── parameters.yaml
+    └── project
+        └── ...
+└── my_data
+    ├── data
+       ├── ...
+    └── labels
+        └── ...
+

+

Now, you can execute the commands below, being located at data_preparator_mlcube/mlcube/: +

mlcube run --task prepare data_path=../../my_data/data/ labels_path=../../my_data/labels/
+mlcube run --task sanity_check
+mlcube run --task statistics output_path=../../my_data/statistics.yaml
+

+

Note that:

+
    +
  1. The passed paths are relative to mlcube/workspace rather then to the current working directory,
  2. +
  3. We used default values for transformed data so new folders would appear: mlcube/workspace/data/ and others.
  4. +
+
+

Using the Example with GPUs

+

The provided example codebase runs only on CPU. You can modify it to pass a GPU inside Docker image if your code utilizes it.

+

The general instructions for building an MLCube to work with a GPU are the same as the provided instructions, but with the following slight modifications:

+
    +
  • Enter a number different than 0 for the accelerator_count that you will be prompted with when creating the MLCube template or modify platform.accelerator_count value of mlcube.yaml configuration.
  • +
  • Inside the docker section of the mlcube.yaml, add a key value pair: gpu_args: --gpus=all. These gpu_args will be passed to docker run command by MLCube. You may add more than just --gpus=all.
  • +
  • Make sure you install the required GPU dependencies in the docker image. For instance, this may be done by simply modifying the pip dependencies in the requirements.txt file to download pytorch with cuda, or by changing the base image of the dockerfile.
  • +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/mlcube_metrics/index.html b/mlcubes/mlcube_metrics/index.html new file mode 100644 index 000000000..fecf040c2 --- /dev/null +++ b/mlcubes/mlcube_metrics/index.html @@ -0,0 +1,893 @@ + + + + + + + + + + + + + + + + + + + + + + + + Creating Metrics MLCubes from Scratch - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

In Progress

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/mlcube_metrics_WIP/index.html b/mlcubes/mlcube_metrics_WIP/index.html new file mode 100644 index 000000000..b5bdf7ff6 --- /dev/null +++ b/mlcubes/mlcube_metrics_WIP/index.html @@ -0,0 +1,1178 @@ + + + + + + + + + + + + + + + + + + + + mlcube metrics WIP - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

TODO: Change the structure to align with mlcube_models, to help users wrap their existing code into mlcube

+

Metrics/Evaluator MLCube

+

Introduction

+

This guide is one of three designed to assist users in building MedPerf-compatible MLCubes. The other two guides focus on creating a Data Preparator MLCube and a Model MLCube. Together, these three MLCubes form a complete benchmark workflow for the task of thoracic disease detection from Chest X-rays.

+

In summary, a functional MedPerf pipeline includes these steps:

+
    +
  1. The Data Owner exports a raw dataset from their databases (manually or via scripts) - this step occurs outside the pipeline itself. Lets name the output folder as my_raw_data/. If the pipeline is run by another person (Model Owner/Benchmark Owner), a predefined my_benchmark_demo_raw_data/ would be used instead (created and distributed by the Benchmark Owner).
  2. +
  3. The Data Preparator MLCube takes this folder's path as input and converts data into a standardized format, resulting in some my_prepared_dataset/ folder (MLCube is implemented by the Benchmark Owner).
  4. +
  5. The Model MLCube processes the prepared data, running a model and saving the results in some my_model_predictions/ folder (MLCube is implemented by the Model Owner; the Benchmark Owner must implement a baseline model MLCube to be used as a mock-up).
  6. +
  7. The Metrics/Evaluator MLCube processes predictions and evaluates metrics, saving them in some my_metrics.yaml file (MLCube implemented by the Benchmark Owner).
  8. +
  9. The Data Owner reviews the metric results and may submit them to the MedPerf server.
  10. +
+

Aforementioned guides detail steps 2-4. As all steps demonstrate building specific MLCubes, we recommend starting with the Model MLCube guide, which offers a more detailed explanation of the MLCube's concept and structure. Another option is to explore MLCube basic docs. In this guide provides the shortened concepts description, focusing on nuances and input/output parameters.

+

About this Guide

+

This guide describes the tasks, structure and input/output parameters of Metrics MLCube, allowing users at the end to be able to implement their own MedPerf-compatible MLCube for Benchmark purposes.

+

The guide starts with general advices, steps, and the required API for building these MLCubes. Subsequently, it will lead you through creating your MLCube using the Chest X-ray Data Preprocessor MLCube as a practical example.

+

Note: As the Dataset Owner would share the output of your metrics evaluation with you as Benchmark Owner, ensure that your metrics are not too specific and do not reveal any Personally Identifiable Information (PII) or other confidential data (including dataset statistics) - otherwise, no Dataset Owners would agree to participate in your benchmark.

+

Before Building the MLCube

+

Your MLCube must implement an evaluate command that calculates your metrics.

+

It's assumed that you as Benchmark Owner already have:

+
    +
  • Some raw data.
  • +
  • Implemented Data Preparator and a Baseline Model MLCubes as they are foundational to Benchmark pipeline.
  • +
  • Model predictions are stored at some my_model_predictions/ folder.
  • +
  • A working implementation of metric calculations.
  • +
+

This guide will help you encapsulate your preparation code within an MLCube. Make sure you extracted metric calculation logic, so it can be executed independently.

+

Required API

+

During execution, the evaluation command will receive specific parameters. While you are flexible in code implementation, keep in mind that your implementation will receive the following input arguments:

+
    +
  • predictions: the path to the folder containing your predictions (read-only).
  • +
  • labels: the path to the folder containing transformed ground truth labels (read-only).
  • +
  • Any other optional extra params that you attach to the MLCube, such as parameters file. Note: these extra parameters contain values defined by you, the MLCube owner, not the users' data.
  • +
  • output_path: path to .yaml file where your code should write down calculated metrics.
  • +
+

Build Your Own MLCube

+

While this guide leads you through creating your own MLCube, you can always check a prebuilt example for a better understanding of how it works in an already implemented MLCube. The example is available here: +

cd examples/chestxray_tutorial/metrics/
+

+

The guide uses this implementation to describe concepts.

+

Use an MLCube Template

+

First, ensure you have MedPerf installed. Create a Metrics MLCube template by running the following command:

+
medperf mlcube create evaluator
+
+

You will be prompted to fill in some configuration options through the CLI. Below are the options and their default values:

+
project_name [Evaluator MLCube]: # (1)!
+project_slug [evaluator_mlcube]: # (2)!
+description [Evaluator MLCube Template. Provided by MLCommons]: # (3)!
+author_name [John Smith]: # (4)!
+accelerator_count [0]: # (5)!
+docker_image_name [docker/image:latest]: # (6)!
+
+
    +
  1. Gives a Human-readable name to the MLCube Project.
  2. +
  3. Determines how the MLCube root folder will be named.
  4. +
  5. Gives a Human-readable description to the MLCube Project.
  6. +
  7. Documents the MLCube implementation by specifying the author.
  8. +
  9. Specifies how many GPUs should be visible by the MLCube.
  10. +
  11. MLCubes use Docker containers under the hood. Here, you can provide an image tag for the image created by this MLCube. You should use a valid name that allows you to upload it to a Docker registry.
  12. +
+

After filling the configuration options, the following directory structure will be generated:

+
.
+└── data_preparator_mlcube
+    ├── mlcube
+       ├── mlcube.yaml
+       └── workspace
+           └── parameters.yaml
+    └── project
+        ├── Dockerfile
+        ├── mlcube.py
+        └── requirements.txt
+
+

The project Folder

+

This is where your metrics logic will live. It contains a standard Docker image project with a specific API for the entrypoint. mlcube.py contains the entrypoint and handles the evaluate task. Update this template with your code and bind your logic to specified command entry-point function. +Refer to the Chest X-ray tutorial example for an example of how it should look:

+
mlcube.py
"""MLCube handler file"""
+import typer
+import yaml
+from metrics import calculate_metrics
+
+app = typer.Typer()
+
+
+@app.command("evaluate")
+def evaluate(
+    labels: str = typer.Option(..., "--labels"),
+    predictions: str = typer.Option(..., "--predictions"),
+    parameters_file: str = typer.Option(..., "--parameters_file"),
+    output_path: str = typer.Option(..., "--output_path"),
+):
+    with open(parameters_file) as f:
+        parameters = yaml.safe_load(f)
+
+    calculate_metrics(labels, predictions, parameters, output_path)
+
+
+@app.command("hotfix")
+def hotfix():
+    # NOOP command for typer to behave correctly. DO NOT REMOVE OR MODIFY
+    pass
+
+
+if __name__ == "__main__":
+    app()
+
+

The mlcube Folder

+

This folder is primarily for configuring your MLCube and providing additional files the MLCube may interact with, such as parameters or model weights.

+

mlcube.yaml MLCube Configuration

+

The mlcube/mlcube.yaml file contains metadata and configuration of your mlcube. This file is already populated with the configuration you provided during the template creation step. There is no need to edit anything in this file except if you are specifying extra parameters to the commands.

+
mlcube.py
name: Classification Metrics
+description: MedPerf Tutorial - Metrics MLCube.
+authors:
+  - { name: MLCommons Medical Working Group }
+
+platform:
+  accelerator_count: 0
+
+docker:
+  # Image name
+  image: mlcommons/chestxray-tutorial-metrics:0.0.0
+  # Docker build context relative to $MLCUBE_ROOT. Default is `build`.
+  build_context: "../project"
+  # Docker file name within docker build context, default is `Dockerfile`.
+  build_file: "Dockerfile"
+
+tasks:
+  evaluate:
+    # Computes evaluation metrics on the given predictions and ground truths
+    parameters:
+      inputs:
+        {
+          predictions: predictions,
+          labels: labels,
+          parameters_file: parameters.yaml,
+        }
+      outputs: { output_path: { type: "file", default: "results.yaml" } }
+
+

All paths are relative to mlcube/workspace/ folder.

+

To set up additional inputs, add a key-value pair in the task's inputs dictionary:

+
mlcube.yaml
...
+  prepare:
+    parameters:
+      inputs:
+        {
+          predictions: predictions,
+          labels: labels,
+          parameters_file: parameters.yaml,
+          some_additional_file_with_weights: additional_files/my_weights.zip
+        }
+      outputs: { output_path: { type: "file", default: "results.yaml" } }
+...
+
+

Considering the note about path locations, this new file should be stored at mlcube/workspace/additional_files/my_weights.zip.

+

Parameters

+

Your metrics evaluation logic might depend on certain parameters (e.g., proba threshold for classifying predictions). It is generally better to pass such parameters when running the MLCube, rather than hardcoding them. This can be done via a parameters.yaml file that is passed to the MLCube. You can parse this file in the mlcube.py file and pass its contents to your logic.

+

This file should be placed in the mlcube/workspace folder.

+

Build Your MLCube

+

After you follow the previous sections and fulfill the image with your logic, the MLCube is ready to be built and run. Run the command below to build the MLCube. Ensure you are in the mlcube/ subfolder of your Evaluator.

+
mlcube configure -Pdocker.build_strategy=always
+
+

This command builds your Docker image and prepares the MLCube for use.

+

Run Your MLCube

+

MedPerf will take care of running your MLCube. However, it's recommended to test the MLCube alone before using it with MedPerf for better debugging.

+

To run the MLCube, use the command below. Ensure you are located in the mlcube/ subfolder of your Data Preparator.

+
mlcube run --task evaluate predictions=<path_to_predictions> \
+ labels=<path_to_transformed_labels> \
+ output_path=<path_to_yaml_file_to_save>
+
+
+

Relative paths

+

Keep in mind that though we are running tasks from mlcube/, all the paths should be absolute or relative to mlcube/workspace/.

+
+
+

Default values

+

Default values are set for every path parameter, allowing for their omission in commands. For example, in the discussed Chest X-Ray example, the predictions input is defined as follows: +

...
+  inputs:
+    {
+      predictions: predictions,
+      labels: labels,
+    }
+...
+

+

If this parameter is omitted (e.g., running MLCube with default parameters by mlcube run --task evaluate), it's assumed that predictions are stored in the mlcube/workspace/predictions/ folder.

+
+

Using the Example with GPUs

+

The provided example codebase runs only on CPU. You can modify it to pass a GPU inside Docker image if your code utilizes it.

+

The general instructions for building an MLCube to work with a GPU are the same as the provided instructions, but with the following slight modifications:

+
    +
  • Enter a number different than 0 for the accelerator_count that you will be prompted with when creating the MLCube template or modify platform.accelerator_count value of mlcube.yaml configuration.
  • +
  • Inside the docker section of the mlcube.yaml, add a key value pair: gpu_args: --gpus=all. These gpu_args will be passed to docker run command by MLCube. You may add more than just --gpus=all.
  • +
  • Make sure you install the required GPU dependencies in the docker image. For instance, this may be done by simply modifying the pip dependencies in the requirements.txt file to download pytorch with cuda, or by changing the base image of the dockerfile.
  • +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/mlcube_models/index.html b/mlcubes/mlcube_models/index.html new file mode 100644 index 000000000..01cdfe303 --- /dev/null +++ b/mlcubes/mlcube_models/index.html @@ -0,0 +1,1858 @@ + + + + + + + + + + + + + + + + + + + + + + + + Creating Model MLCubes from Scratch - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Model MLCube

+

Introduction

+

This is one of the three guides that help the user build MedPerf-compatible MLCubes. The other two guides are for building a Data Preparator MLCube and a Metrics MLCube. Together, the three MLCubes examples constitute a complete benchmark workflow for the task of thoracic disease detection from Chest X-rays.

+

About this Guide

+

This guide will help users familiarize themselves with the expected interface of the Model MLCube and gain a comprehensive understanding of its components. By following this walkthrough, users will gain insights into the structure and organization of a Model MLCube, allowing them at the end to be able to implement their own MedPerf-compatible Model MLCube.

+

The guide will start by providing general advice, steps, and hints on building these MLCubes. Then, an example will be presented through which the provided guidance will be applied step-by-step to build a Chest X-ray classifier MLCube. The final MLCube code can be found here.

+

Before Building the MLCube

+

It is assumed that you already have a working code that runs inference on data and generates predictions, and what you want to accomplish through this guide is to wrap your inference code within an MLCube.

+
    +
  • Make sure you decouple your inference logic from the other machine learning common pipelines (e.g.; training, metrics, ...).
  • +
  • Your inference logic can be written in any structure, can be split into any number of files, can represent any number of inference stages, etc..., as long as the following hold:
      +
    • The whole inference flow can be invoked by a single command/function.
    • +
    • This command/function has at least the following arguments:
        +
      • A string representing a path that points to all input data records
      • +
      • A string representing a path that points to the desired output directory where the predictions will be stored.
      • +
      +
    • +
    +
  • +
  • Your inference logic should not alter the input files and folders.
  • +
  • Your inference logic should expect the input data in a certain structure. This is usually determined by following the specifications of the benchmark you want to participate in.
  • +
  • Your inference logic should save the predictions in the output directory in a certain structure. This is usually determined by following the specifications of the benchmark you want to participate in.
  • +
+

Use an MLCube Template

+

MedPerf provides MLCube templates. You should start from a template for faster implementation and to build MLCubes that are compatible with MedPerf.

+

First, make sure you have MedPerf installed. You can create a model MLCube template by running the following command:

+
medperf mlcube create model
+
+

You will be prompted to fill in some configuration options through the CLI. Below are the options and their default values:

+
project_name [Model MLCube]: # (1)!
+project_slug [model_mlcube]: # (2)!
+description [Model MLCube Template. Provided by MLCommons]: # (3)!
+author_name [John Smith]: # (4)!
+accelerator_count [0]: # (5)!
+docker_image_name [docker/image:latest]: # (6)!
+
+
    +
  1. Gives a Human-readable name to the MLCube Project.
  2. +
  3. Determines how the MLCube root folder will be named.
  4. +
  5. Gives a Human-readable description to the MLCube Project.
  6. +
  7. Documents the MLCube implementation by specifying the author.
  8. +
  9. Indicates how many GPUs should be visible by the MLCube.
  10. +
  11. MLCubes use Docker containers under the hood. Here, you can provide an image tag to the image that will be created by this MLCube. You should use a valid name that allows you to upload it to a Docker registry.
  12. +
+

After filling the configuration options, the following directory structure will be generated:

+
.
+└── model_mlcube
+    ├── mlcube
+       ├── mlcube.yaml
+       └── workspace
+           └── parameters.yaml
+    └── project
+        ├── Dockerfile
+        ├── mlcube.py
+        └── requirements.txt
+
+

The next sections will go through the contents of this directory in details and customize it.

+

The project folder

+

This is where your inference logic will live. This folder initially contains three files as shown above. The upcoming sections will cover their use in details.

+

The first thing to do is put your code files in this folder.

+

How will the MLCube identify your code?

+

This is done through the mlcube.py file. This file defines the interface of the MLCube and should be linked to your inference logic.

+
mlcube.py
"""MLCube handler file"""
+import typer
+
+app = typer.Typer()
+
+
+@app.command("infer")
+def infer(
+    data_path: str = typer.Option(..., "--data_path"),
+    parameters_file: str = typer.Option(..., "--parameters_file"),
+    output_path: str = typer.Option(..., "--output_path"),
+    # Provide additional parameters as described in the mlcube.yaml file
+    # e.g. model weights:
+    # weights: str = typer.Option(..., "--weights"),
+):
+    # Modify the prepare command as needed
+    raise NotImplementedError("The evaluate method is not yet implemented")
+
+
+@app.command("hotfix")
+def hotfix():
+    # NOOP command for typer to behave correctly. DO NOT REMOVE OR MODIFY
+    pass
+
+
+if __name__ == "__main__":
+    app()
+
+

As shown above, this file exposes a command infer. It's basic arguments are the input data path, the output predictions path, and a parameters file path.

+

The parameters file, as will be explained in the upcoming sections, gives flexibility to your MLCube. For example, instead of hardcoding the inference batch size in the code, it can be configured by passing a parameters file to your MLCube which contains its value. This way, your same MLCube can be reused with multiple batch sizes by just changing the input parameters file.

+

You should ignore the hotfix command as described in the file.

+

The infer command will be automatically called by the MLCube when it's built and run. This command should call your inference logic. Make sure you replace its contents with a code that calls your inference logic. This could be by importing a function from your code files and calling it with the necessary arguments.

+

Prepare your Dockerfile

+

The MLCube will execute a docker image whose entrypoint is mlcube.py. The MLCube will first build this image from the Dockerfile specified in the project folder. You can customize the Dockerfile however you want as long as the entrypoint is runs the mlcube.py file

+

Make sure you include in your Dockerfile any system dependency your code depends on. It is also common to have pip dependencies, make sure you install them in the Dockerfile as well.

+

Below is the docker file provided in the template:

+
Dockerfile
FROM python:3.9.16-slim
+
+COPY ./requirements.txt /mlcube_project/requirements.txt 
+
+RUN pip3 install --no-cache-dir -r /mlcube_project/requirements.txt
+
+ENV LANG C.UTF-8
+
+COPY . /mlcube_project
+
+ENTRYPOINT ["python3", "/mlcube_project/mlcube.py"]
+
+

As shown above, this docker file makes sure python is available by using the python base image, installs pip dependencies using the requirements.txt file, and sets the entrypoint to run mlcube.py. Note that the MLCube tool will invoke the Docker build command from the project folder, so it will copy all your files found in the project to the Docker image.

+

The mlcube folder

+

This folder is mainly for configuring your MLCube and providing additional files the MLCube may interact with, such as parameters or model weights.

+

Include additional input files

+
parameters
+

Your inference logic may depend on some parameters (e.g. inference batch size). It is usually a more favorable design to not hardcode such parameters, but instead pass them when running the MLCube. This can be done by having a parameters.yaml file as an input to the MLCube. This file will be available to the infer command described before. You can parse this file in the mlcube.py file and pass its contents to your code.

+

This file should be placed in the mlcube/workspace folder.

+
model weights
+

It is a good practice not to ship your model weights within the docker image to reduce the image size and provide flexibility of running the MLCube with different model weights. To do this, model weights path should be provided as a separate parameter to the MLCube. You should place your model weights in a folder named additional_files inside the mlcube/workspace folder. This is how MedPerf expects any additional input to your MLCube beside the data path and the paramters file.

+

After placing your model weights in mlcube/workspace/additional_files, you have to modify two files:

+
    +
  • mlcube.py: add an argument to the infer command which will correspond to the path of your model weights. Remember also to pass this argument to your inference logic.
  • +
  • mlcube.yaml: The next section introduces this file and describes it in details. You should add your extra input arguments to this file as well, as described below.
  • +
+

Configure your MLCube

+

The mlcube.yaml file contains metadata and configuration of your mlcube. This file was already populated with the configuration you provided during the step of creating the template. There is no need to edit anything in this file except if you are specifying extra parameters to the infer command (e.g., model weights as described in the previous section).

+

You will be modifying the tasks section of the mlcube.yaml file in order to account for extra additional inputs:

+
mlcube.yaml
tasks:
+  infer:
+    # Computes predictions on input data
+    parameters:
+      inputs: {
+          data_path: data/,
+          parameters_file: parameters.yaml,
+          # Feel free to include other files required for inference.
+          # These files MUST go inside the additional_files path.
+          # e.g. model weights
+          # weights: additional_files/weights.pt,
+        }
+      outputs: { output_path: { type: directory, default: predictions } }
+
+

As hinted by the comments as well, you can add the additional parameters by specifying an extra key-value pair in the inputs dictionary of the infer task.

+

Build your MLCube

+

After you follow the previous sections, the MLCube is ready to be built and run. Run the command below to build the MLCube. Make sure you are in the folder model_mlcube/mlcube.

+
mlcube configure -Pdocker.build_strategy=always
+
+

This command will build your docker image and make the MLCube ready to use.

+

Run your MLCube

+

MedPerf will take care of running your MLCube. However, it's recommended to test the MLCube alone before using it with MedPerf for better debugging.

+

Use the command below to run the MLCube. Make sure you are in the folder model_mlcube/mlcube.

+
mlcube run --task infer data_path=<absolute path to input data> output_path=<absolute path to a folder where predictions will be saved>
+
+

A Working Example

+

Assume you have the codebase below. This code can be used to predict thoracic diseases based on Chest X-ray data. The classification task is modeled as a multi-label classification class.

+
+models.py +
"""
+Taken from MedMNIST/MedMNIST.
+"""
+
+import torch.nn as nn
+
+
+class SimpleCNN(nn.Module):
+    def __init__(self, in_channels, num_classes):
+        super(SimpleCNN, self).__init__()
+
+        self.layer1 = nn.Sequential(
+            nn.Conv2d(in_channels, 16, kernel_size=3), nn.BatchNorm2d(16), nn.ReLU()
+        )
+
+        self.layer2 = nn.Sequential(
+            nn.Conv2d(16, 16, kernel_size=3),
+            nn.BatchNorm2d(16),
+            nn.ReLU(),
+            nn.MaxPool2d(kernel_size=2, stride=2),
+        )
+
+        self.layer3 = nn.Sequential(
+            nn.Conv2d(16, 64, kernel_size=3), nn.BatchNorm2d(64), nn.ReLU()
+        )
+
+        self.layer4 = nn.Sequential(
+            nn.Conv2d(64, 64, kernel_size=3), nn.BatchNorm2d(64), nn.ReLU()
+        )
+
+        self.layer5 = nn.Sequential(
+            nn.Conv2d(64, 64, kernel_size=3, padding=1),
+            nn.BatchNorm2d(64),
+            nn.ReLU(),
+            nn.MaxPool2d(kernel_size=2, stride=2),
+        )
+
+        self.fc = nn.Sequential(
+            nn.Linear(64 * 4 * 4, 128),
+            nn.ReLU(),
+            nn.Linear(128, 128),
+            nn.ReLU(),
+            nn.Linear(128, num_classes),
+        )
+
+    def forward(self, x):
+        x = self.layer1(x)
+        x = self.layer2(x)
+        x = self.layer3(x)
+        x = self.layer4(x)
+        x = self.layer5(x)
+        x = x.view(x.size(0), -1)
+        x = self.fc(x)
+        return x
+
+
+
+data_loader.py +
import numpy as np
+import torchvision.transforms as transforms
+import os
+from torch.utils.data import Dataset
+
+
+class CustomImageDataset(Dataset):
+    def __init__(self, data_path):
+        self.transform = transforms.Compose(
+            [transforms.ToTensor(), transforms.Normalize(mean=[0.5], std=[0.5])]
+        )
+        self.files = os.listdir(data_path)
+        self.data_path = data_path
+
+    def __len__(self):
+        return len(self.files)
+
+    def __getitem__(self, idx):
+        img_path = os.path.join(self.data_path, self.files[idx])
+        image = np.load(img_path, allow_pickle=True)
+        image = self.transform(image)
+        file_id = self.files[idx].strip(".npy")
+        return image, file_id
+
+
+
+infer.py +
import torch
+from models import SimpleCNN
+from tqdm import tqdm
+from torch.utils.data import DataLoader
+from data_loader import CustomImageDataset
+from pprint import pprint
+
+
+data_path = "path/to/data/folder"
+weights = "path/to/weights.pt"
+in_channels = 1
+num_classes = 14
+batch_size = 5
+
+# load model
+model = SimpleCNN(in_channels=in_channels, num_classes=num_classes)
+model.load_state_dict(torch.load(weights))
+model.eval()
+
+# load prepared data
+dataset = CustomImageDataset(data_path)
+dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
+
+# inference
+predictions_dict = {}
+with torch.no_grad():
+    for images, files_ids in tqdm(dataloader):
+        outputs = model(images)
+        outputs = torch.nn.Sigmoid()(outputs)
+        outputs = outputs.detach().numpy()
+
+        for file_id, output in zip(files_ids, outputs):
+            predictions_dict[file_id] = output
+
+pprint(predictions_dict)
+
+
+

Throughout the next sections, this code will be wrapped within an MLCube.

+

Before Building the MLCube

+

The guidlines listed previously in this section will now be applied to the given codebase. Assume that you were instructed by the benchmark you are participating with to have your MLCube interface as follows:

+
    +
  • The MLCube should expect the input data folder to contain a list of images as numpy files.
  • +
  • The MLCube should save the predictions in a single JSON file as key-value pairs of image file ID and its corresponding prediction. A prediction should be a vector of length 14 (number of classes) and has to be the output of the Sigmoid activation layer.
  • +
+

It is important to make sure that your MLCube will output an expected predictions format and consume a defined data format, since it will be used in a benchmarking pipeline whose data input is fixed and whose metrics calculation logic expects a fixed predictions format.

+

Considering the codebase above, here are the things that should be done before proceeding to build the MLCube:

+
    +
  • infer.py only prints predictions but doesn't store them. This has to be changed.
  • +
  • infer.py hardcodes some parameters (num_classes, in_channels, batch_size) as well as the path to the trained model weights. Consider making these items configurable parameters. (This is optional but recommended)
  • +
  • Consider refactoring infer.py to be a function so that is can be easily called by mlcube.py.
  • +
+

The other files models.py and data_loader.py seem to be good already. The data loader expects a folder containing a list of numpy arrays, as instructed.

+

Here is the modified version of infer.py according to the points listed above:

+
+infer.py (Modified) +
import torch
+import os
+from models import SimpleCNN
+from tqdm import tqdm
+from torch.utils.data import DataLoader
+from data_loader import CustomImageDataset
+import json
+
+
+def run_inference(data_path, parameters, output_path, weights):
+    in_channels = parameters["in_channels"]
+    num_classes = parameters["num_classes"]
+    batch_size = parameters["batch_size"]
+
+    # load model
+    model = SimpleCNN(in_channels=in_channels, num_classes=num_classes)
+    model.load_state_dict(torch.load(weights))
+    model.eval()
+
+    # load prepared data
+    dataset = CustomImageDataset(data_path)
+    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
+
+    # inference
+    predictions_dict = {}
+    with torch.no_grad():
+        for images, files_ids in tqdm(dataloader):
+            outputs = model(images)
+            outputs = torch.nn.Sigmoid()(outputs)
+            outputs = outputs.detach().numpy().tolist()
+
+            for file_id, output in zip(files_ids, outputs):
+                predictions_dict[file_id] = output
+
+    # save
+    preds_file = os.path.join(output_path, "predictions.json")
+    with open(preds_file, "w") as f:
+        json.dump(predictions_dict, f, indent=4)
+
+
+

Create an MLCube Template

+

Assuming you installed MedPerf, run the following:

+
medperf mlcube create model
+
+

You will be prompted to fill in the configuration options. Use the following configuration as a reference:

+
project_name [Model MLCube]: Custom CNN Classification Model
+project_slug [model_mlcube]: model_custom_cnn
+description [Model MLCube Template. Provided by MLCommons]: MedPerf Tutorial - Model MLCube.
+author_name [John Smith]: <use your name>
+accelerator_count [0]: 0
+docker_image_name [docker/image:latest]: repository/model-tutorial:0.0.0
+
+
+

Note

+

This example is built to be used with a CPU. See the last section to know how to configure this example with a GPU.

+
+

Note that docker_image_name is arbitrarily chosen. Use a valid docker image.

+

Move your Codebase

+

Move the three files of the codebase to the project folder. The directory tree will then look like this:

+
.
+└── model_custom_cnn
+    ├── mlcube
+       ├── mlcube.yaml
+       └── workspace
+           └── parameters.yaml
+    └── project
+        ├── Dockerfile
+        ├── mlcube.py
+        ├── models.py
+        ├── data_loader.py
+        ├── infer.py
+        └── requirements.txt
+
+

Add your parameters and model weights

+

Since num_classes, in_channels, and batch_size are now parametrized, they should be defined in workspace/parameters.yaml. Also, the model weights should be placed inside workspace/additional_files.

+

Add parameters

+

Modify parameters.yaml to include the following:

+
parameters.yaml
in_channels: 1
+num_classes: 14
+batch_size: 5
+
+

Add model weights

+

Download the following model weights to use in this example: Click here to Download

+

Extract the file to workspace/additional_files. The directory tree should look like this:

+
.
+└── model_custom_cnn
+    ├── mlcube
+       ├── mlcube.yaml
+       └── workspace
+           ├── additional_files
+              └── cnn_weights.pth
+           └── parameters.yaml
+    └── project
+        ├── Dockerfile
+        ├── mlcube.py
+        ├── models.py
+        ├── data_loader.py
+        ├── infer.py
+        └── requirements.txt
+
+

Modify mlcube.py

+

Next, the inference logic should be triggered from mlcube.py. The parameters_file will be read in mlcube.py and passed as a dictionary to the inference logic. Also, an extra parameter weights is added to the function signature which will correspond to the model weights path. See below the modified mlcube.py file.

+
+mlcube.py (Modified) +
"""MLCube handler file"""
+import typer
+import yaml
+
+from infer import run_inference
+
+app = typer.Typer()
+
+
+@app.command("infer")
+def infer(
+    data_path: str = typer.Option(..., "--data_path"),
+    parameters_file: str = typer.Option(..., "--parameters_file"),
+    output_path: str = typer.Option(..., "--output_path"),
+    weights: str = typer.Option(..., "--weights"),
+):
+    with open(parameters_file) as f:
+        parameters = yaml.safe_load(f)
+
+    run_inference(data_path, parameters, output_path, weights)
+
+
+@app.command("hotfix")
+def hotfix():
+    # NOOP command for typer to behave correctly. DO NOT REMOVE OR MODIFY
+    pass
+
+
+if __name__ == "__main__":
+    app()
+
+
+

Prepare the Dockerfile

+

The provided Dockerfile in the template is enough and preconfigured to download pip dependencies from the requirements.txt file. All that is needed is to modify the requirements.txt file to include the project's pip dependencies.

+
requirements.txt
typer==0.9.0
+numpy==1.24.3
+PyYAML==6.0
+torch==2.0.1
+torchvision==0.15.2
+tqdm==4.65.0
+--extra-index-url https://download.pytorch.org/whl/cpu
+
+

Modify mlcube.yaml

+

Since the extra parameter weights was added to the infer task in mlcube.py, this has to be reflected on the defined MLCube interface in the mlcube.yaml file. Modify the tasks section to include an extra input parameter: weights: additional_files/cnn_weights.pth.

+
+

Tip

+

The MLCube tool interprets these paths as relative to the workspace.

+
+

The tasks section will then look like this:

+
mlcube.yaml
tasks:
+  infer:
+    # Computes predictions on input data
+    parameters:
+      inputs:
+        {
+          data_path: data/,
+          parameters_file: parameters.yaml,
+          weights: additional_files/cnn_weights.pth,
+        }
+      outputs: { output_path: { type: directory, default: predictions } }
+
+

Build your MLCube

+

Run the command below to create the MLCube. Make sure you are in the folder model_custom_cnn/mlcube.

+
mlcube configure -Pdocker.build_strategy=always
+
+

This command will build your docker image and make the MLCube ready to use.

+
+

Tip

+

Run docker image ls to see your built Docker image.

+
+

Run your MLCube

+

Download a sample data to run on: Click here to Download

+

Extract the data. You will get a folder sample_prepared_data containing a list chest X-ray images as numpy arrays.

+

Use the command below to run the MLCube. Make sure you are in the the folder model_custom_cnn/mlcube.

+
mlcube run --task infer data_path=<absolute path to `sample_prepared_data`> output_path=<absolute path to a folder where predictions will be saved>
+
+

Using the Example with GPUs

+

The provided example codebase runs only on CPU. You can modify it to have pytorch run inference on a GPU.

+

The general instructions for building an MLCube to work with a GPU are the same as the provided instructions, but with the following slight modifications:

+
    +
  • Use a number different than 0 for the accelerator_count that you will be prompted with when creating the MLCube template.
  • +
  • Inside the docker section of the mlcube.yaml, add a key value pair: gpu_args: --gpus=all. These gpu_args will be passed to docker run under the hood by MLCube. You may add more than just --gpus=all.
  • +
  • Make sure you install the required GPU dependencies in the docker image. For instance, this may be done by simply modifying the pip dependencies in the requirements.txt file to download pytorch with cuda, or by changing the base image of the dockerfile.
  • +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/mlcubes/index.html b/mlcubes/mlcubes/index.html new file mode 100644 index 000000000..a31c9553d --- /dev/null +++ b/mlcubes/mlcubes/index.html @@ -0,0 +1,998 @@ + + + + + + + + + + + + + + + + + + + + + + + + Introduction - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

MedPerf MLCubes

+

MLCube is a set of common conventions for creating Machine Learning (ML) software that can "plug-and-play" on many different systems. It is basically a container image with a simple interface and the correct metadata that allows researchers and developers to easily share and experiment with ML pipelines.

+

You can read more about MLCubes here.

+

In MedPerf, MLCubes are required for creating the three technical components of a benchmarking experiment: the data preparation flow, the model inference flow, and the evaluation flow. A Benchmark Committee will be required to create three MLCubes that implement these components. A Model Owner will be required to wrap their model code within an MLCube in order to submit it to the MedPerf server and participate in a benchmark.

+

MLCubes are general-purpose. MedPerf defines three specific design types of MLCubes according to their purpose: The Data Preparator MLCube, the Model MLCube, and the Metrics MLCube. Each type has a specific MLCube task configuration that defines the MLCube's interface. Users need to follow these design specs when building their MLCubes to be conforming with MedPerf. We provide below a high-level description of each MLCube type and a link to a guide for building an example for each type.

+

Data Preparator MLCube

+

The Data Preparator MLCube is used to prepare the data for executing the benchmark. Ideally, it can receive different data standards for the task at hand, transforming them into a single, unified standard. Additionally, it ensures the quality and compatibility of the data and computes statistics and metadata for registration purposes.

+

This MLCube's interface should expose the following tasks:

+
    +
  • +

    Prepare: Transforms the input data into the expected output data standard. It receives as input the location of the original data, as well as the location of the labels, and outputs the prepared dataset and accompanying labels.

    +
  • +
  • +

    Sanity check: Ensures the integrity of the prepared data. It may check for anomalies and data corruption (e.g. blank images, empty test cases). It constitutes a set of conditions the prepared data should comply with.

    +
  • +
  • +

    Statistics: Computes statistics on the prepared data.

    +
  • +
+

Check this guide on how to create a Data Preparation MLCube.

+

Model MLCube

+

The model MLCube contains a pre-trained machine learning model that is going to be evaluated by the benchmark. It's interface should expose the following task:

+
    +
  • Infer: Obtains predictions on the prepared data. It receives as input the location of the prepared data and outputs the predictions.
  • +
+

Check this guide on how to create a Model MLCube.

+

Metrics/Evaluator MLCube

+

The Metrics MLCube is used for computing metrics on the model predictions by comparing them against the provided labels. It's interface should expose the following task:

+
    +
  • Evaluate: Computes the metrics. It receives as input the location of the predictions and the location of the prepared data labels and generates a yaml file containing the metrics.
  • +
+

Check this guide on how to create a Metrics MLCube.

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/build/index.html b/mlcubes/shared/build/index.html new file mode 100644 index 000000000..6bac10937 --- /dev/null +++ b/mlcubes/shared/build/index.html @@ -0,0 +1,931 @@ + + + + + + + + + + + + + + + + + + + + Build - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Build

+ +

Building a {{ no such element: dict object['name'] }}

+

The following section will describe how you can create a {{ no such element: dict object['name'] }} from scratch. This documentation goes through the set of commands provided to help during this process, as well as the contents of a {{ no such element: dict object['name'] }}.

+

Setup

+

MedPerf provides some cookiecutter templates for all the related MLCubes. Additionally, it provides commands to easily retreive and use these templates. For that, you need to make sure MedPerf is installed. If you haven not done so, please follow the steps below:

+
    +
  1. +

    Clone the repository: +

    git clone https://github.com/mlcommons/medperf
    +cd medperf
    +

    +
  2. +
  3. +

    Install the MedPerf CLI: +

    pip install -e cli
    +

    +
  4. +
  5. +

    If you have not done so, create a folder for keeping all MLCubes created in this tutorial: +

    mkdir tutorial
    +cd tutorial
    +

    +
  6. +
  7. +

    Create a {{ no such element: dict object['name'] }} through MedPerf: +

    medperf mlcube create {{ no such element: dict object['slug'] }}
    +
    + You should be prompted to fill in some configuration options through the CLI. An example of some good options to provide for this specific task is presented below:

    +
  8. +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/contents/index.html b/mlcubes/shared/contents/index.html new file mode 100644 index 000000000..97df297bd --- /dev/null +++ b/mlcubes/shared/contents/index.html @@ -0,0 +1,893 @@ + + + + + + + + + + + + + + + + + + + + Contents - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Contents

+ +

Contents

+

Let's have a look at what the previous command generated. First, lets look at the whole folder structure: +

tree 
+

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/cookiecutter/index.html b/mlcubes/shared/cookiecutter/index.html new file mode 100644 index 000000000..739f38268 --- /dev/null +++ b/mlcubes/shared/cookiecutter/index.html @@ -0,0 +1,873 @@ + + + + + + + + + + + + + + + + + + + + Cookiecutter - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Cookiecutter

+ +
+

Note

+

MedPerf is running CookieCutter under the hood. This medperf command provides additional arguments for handling different scenarios. You can see more information on this by running medperf mlcube create --help

+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/docker_file/index.html b/mlcubes/shared/docker_file/index.html new file mode 100644 index 000000000..26d3f0cc9 --- /dev/null +++ b/mlcubes/shared/docker_file/index.html @@ -0,0 +1,895 @@ + + + + + + + + + + + + + + + + + + + + Docker file - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Docker file

+ +

project/Dockerfile

+

MLCubes rely on containers to work. By default, Medperf provides a functional Dockerfile, which uses ubuntu:18.0.4 and python3.6. This Dockerfile handles all the required procedures to install your project and redirect commands to the project/mlcube.py file. You can modify as you see fit, as long as the entry point behaves as a CLI, as described before.

+
+

Running Docker MLCubes with Singularity

+

If you are building a Docker MLCube and expect it to be also run using Singularity, you need to keep in mind that Singularity containers built from Docker images ignore the WORKDIR instruction if used in Dockerfiles. Make sure you also follow their best practices for writing Singularity-compatible Dockerfiles.

+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/execute/index.html b/mlcubes/shared/execute/index.html new file mode 100644 index 000000000..eebd538fb --- /dev/null +++ b/mlcubes/shared/execute/index.html @@ -0,0 +1,904 @@ + + + + + + + + + + + + + + + + + + + + Execute - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Execute

+ +

Execute

+

Now its time to run our own implementation. We won't go into much detail, since we covered the basics before. But, here are the commands you can run to build and run your MLCube.

+
    +
  1. Go to the MLCube folder. For this, assuming you are in the root of the {{ no such element: dict object['slug'] }}_mlcube, run +
    cd mlcube
    +
  2. +
  3. +

    Build the Docker image using the shortcuts provided by MLCubse. Here is how you can do it: +

    mlcube configure -Pdocker.build_strategy=always # (1)!
    +

    +
      +
    1. MLCube by default will look for the image on Docker hub or locally instead of building it. Providing Pdocker.build_strategy=always enforces MLCube to build the image from source.
    2. +
    +
  4. +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/hello_world/index.html b/mlcubes/shared/hello_world/index.html new file mode 100644 index 000000000..8d5976672 --- /dev/null +++ b/mlcubes/shared/hello_world/index.html @@ -0,0 +1,871 @@ + + + + + + + + + + + + + + + + + + + + Hello world - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Hello world

+ +

In order to provide a basic example of how Medperf MLCubes work under the hood, a toy Hello World benchmark is provided. This benchmark implements a pipeline for ingesting people's names and generating greetings for those names given some criteria. Although this is not the most scientific example, it provides a clear idea of all the pieces required to implement your MLCubes for Medperf.

+

You can find the {{ no such element: dict object['name'] }} code here

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/hotfix/index.html b/mlcubes/shared/hotfix/index.html new file mode 100644 index 000000000..cebcefb4e --- /dev/null +++ b/mlcubes/shared/hotfix/index.html @@ -0,0 +1,875 @@ + + + + + + + + + + + + + + + + + + + + Hotfix - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Hotfix

+ +
+

What is the hotfix function inside mlcube.py?

+

To summarize, this issue is benign and can be safely ignored. It prevents a potential issue with the CLI and does not require further action.

+

If you use the typer/click library for your command-line interface (CLI) and have only one @app.command, the command line may not be parsed as expected by mlcube. This is due to a known issue that can be resolved by adding more than one task to the mlcube interface.

+

To avoid a potential issue with the CLI, we add a dummy typer command to our model cubes that only have one task. If you're not using typer/click, you don't need this dummy command.

+
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/requirements/index.html b/mlcubes/shared/requirements/index.html new file mode 100644 index 000000000..cafeee94e --- /dev/null +++ b/mlcubes/shared/requirements/index.html @@ -0,0 +1,891 @@ + + + + + + + + + + + + + + + + + + + + Requirements - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Requirements

+ +

project/requirements.txt

+

The provided MLCube template assumes your project is python based. Because of this, it provides a requirements.txt file to specify the dependencies to run your project. This file is automatically used by the Dockerfile to install and set up your project. Since some dependencies are necessary, let's add them to the file:

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/mlcubes/shared/setup/index.html b/mlcubes/shared/setup/index.html new file mode 100644 index 000000000..6574fd3d4 --- /dev/null +++ b/mlcubes/shared/setup/index.html @@ -0,0 +1,928 @@ + + + + + + + + + + + + + + + + + + + + Setup - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Setup

+ +

How to run

+

Before digging into the code, let's try manually running the {{ no such element: dict object['name'] }}. During this process, it should be possible to see how MLCube interacts with the folders in the workspace and what is expected to happen during each step:

+

Setup

+
    +
  1. +

    Clone the repository: +

    git clone https://github.com/mlcommons/medperf
    +cd medperf
    +

    +
  2. +
  3. +

    Install mlcube and mlcube-docker using pip: +

    pip install mlcube mlcube-docker
    +

    +
  4. +
  5. +

    Navigate to the HelloWorld directory within the examples folder with +

    cd examples/HelloWorld
    +

    +
  6. +
  7. +

    Change to the current example's mlcube folder with +

    cd {{ no such element: dict object['slug'] }}/mlcube
    +

    +
  8. +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 000000000..8f4894abe Binary files /dev/null and b/objects.inv differ diff --git a/overrides/home.html b/overrides/home.html new file mode 100644 index 000000000..85460378b --- /dev/null +++ b/overrides/home.html @@ -0,0 +1,367 @@ + + +{% extends "main.html" %} +{% block tabs %} +{{ super() }} + + + +
+
+
+
+ +
+

Explore our documentation hub to understand everything you need to get started with MedPerf, + including definitions, setup, tutorials, advanced concepts, and more.

+ +
+
+
+
+
+ + + + + + + + +{% endblock %} +{% block content %}{% endblock %} +{% block footer %}{% endblock %} \ No newline at end of file diff --git a/overrides/partials/header.html b/overrides/partials/header.html new file mode 100644 index 000000000..61b1db1ec --- /dev/null +++ b/overrides/partials/header.html @@ -0,0 +1,157 @@ + + + +{% set class = "md-header" %} +{% if "navigation.tabs.sticky" in features %} + {% set class = class ~ " md-header--lifted" %} +{% endif %} + + +
+ + + + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
\ No newline at end of file diff --git a/reference/SUMMARY/index.html b/reference/SUMMARY/index.html new file mode 100644 index 000000000..735d28aa8 --- /dev/null +++ b/reference/SUMMARY/index.html @@ -0,0 +1,991 @@ + + + + + + + + + + + + + + + + + + + + SUMMARY - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + +

SUMMARY

+ + + + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/_version/index.html b/reference/_version/index.html new file mode 100644 index 000000000..49112f125 --- /dev/null +++ b/reference/_version/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + version - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

version

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/account_management/account_management/index.html b/reference/account_management/account_management/index.html new file mode 100644 index 000000000..b0ce92adf --- /dev/null +++ b/reference/account_management/account_management/index.html @@ -0,0 +1,1016 @@ + + + + + + + + + + + + + + + + + + + + Account management - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Account management

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+get_medperf_user_data() + +

+ + +
+ +

Return cached medperf user data. Get from the server if not found

+ +
+ Source code in cli/medperf/account_management/account_management.py +
73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
def get_medperf_user_data():
+    """Return cached medperf user data. Get from the server if not found"""
+    config_p = read_config()
+    if config.credentials_keyword not in config_p.active_profile:
+        raise MedperfException("You are not logged in")
+
+    medperf_user = config_p.active_profile[config.credentials_keyword].get(
+        "medperf_user", None
+    )
+    if medperf_user is None:
+        medperf_user = set_medperf_user_data()
+
+    return medperf_user
+
+
+
+ +
+ + +
+ + + +

+set_medperf_user_data() + +

+ + +
+ +

Get and cache user data from the MedPerf server

+ +
+ Source code in cli/medperf/account_management/account_management.py +
62
+63
+64
+65
+66
+67
+68
+69
+70
def set_medperf_user_data():
+    """Get and cache user data from the MedPerf server"""
+    config_p = read_config()
+    medperf_user = config.comms.get_current_user()
+
+    config_p.active_profile[config.credentials_keyword]["medperf_user"] = medperf_user
+    write_config(config_p)
+
+    return medperf_user
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/account_management/token_storage/filesystem/index.html b/reference/account_management/token_storage/filesystem/index.html new file mode 100644 index 000000000..57043b96e --- /dev/null +++ b/reference/account_management/token_storage/filesystem/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Filesystem - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Filesystem

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/account_management/token_storage/keyring_/index.html b/reference/account_management/token_storage/keyring_/index.html new file mode 100644 index 000000000..eefc1a9b8 --- /dev/null +++ b/reference/account_management/token_storage/keyring_/index.html @@ -0,0 +1,917 @@ + + + + + + + + + + + + + + + + + + + + Keyring - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Keyring

+ +
+ + + +
+ +

Keyring token storage is NOT used. We used it before this commit but +users who connect to remote machines through passwordless SSH faced some issues.

+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/cli/index.html b/reference/cli/index.html new file mode 100644 index 000000000..91ccdfd7e --- /dev/null +++ b/reference/cli/index.html @@ -0,0 +1,1025 @@ + + + + + + + + + + + + + + + + + + + + Cli - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Cli

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+execute(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='UID of the desired benchmark'), data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), model_uid=typer.Option(..., '--model_uid', '-m', help='UID of model to execute'), approval=typer.Option(False, '-y', help='Skip approval step'), ignore_model_errors=typer.Option(False, '--ignore-model-errors', help='Ignore failing model cubes, allowing for possibly submitting partial results'), no_cache=typer.Option(False, '--no-cache', help='Ignore existing results. The experiment then will be rerun')) + +

+ + +
+ +

Runs the benchmark execution step for a given benchmark, prepared dataset and model

+ +
+ Source code in cli/medperf/cli.py +
35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
@app.command("run")
+@clean_except
+def execute(
+    benchmark_uid: int = typer.Option(
+        ..., "--benchmark", "-b", help="UID of the desired benchmark"
+    ),
+    data_uid: int = typer.Option(
+        ..., "--data_uid", "-d", help="Registered Dataset UID"
+    ),
+    model_uid: int = typer.Option(
+        ..., "--model_uid", "-m", help="UID of model to execute"
+    ),
+    approval: bool = typer.Option(False, "-y", help="Skip approval step"),
+    ignore_model_errors: bool = typer.Option(
+        False,
+        "--ignore-model-errors",
+        help="Ignore failing model cubes, allowing for possibly submitting partial results",
+    ),
+    no_cache: bool = typer.Option(
+        False,
+        "--no-cache",
+        help="Ignore existing results. The experiment then will be rerun",
+    ),
+):
+    """Runs the benchmark execution step for a given benchmark, prepared dataset and model"""
+    result = BenchmarkExecution.run(
+        benchmark_uid,
+        data_uid,
+        [model_uid],
+        ignore_model_errors=ignore_model_errors,
+        no_cache=no_cache,
+    )[0]
+    if result.id:  # TODO: use result.is_registered once PR #338 is merged
+        config.ui.print(  # TODO: msg should be colored yellow
+            """An existing registered result for the requested execution has been\n
+            found. If you wish to submit a new result for the same execution,\n
+            please run the command again with the --no-cache option.\n"""
+        )
+    else:
+        ResultSubmission.run(result.generated_uid, approved=approval)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/association/approval/index.html b/reference/commands/association/approval/index.html new file mode 100644 index 000000000..1a052fc31 --- /dev/null +++ b/reference/commands/association/approval/index.html @@ -0,0 +1,1228 @@ + + + + + + + + + + + + + + + + + + + + Approval - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Approval

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Approval + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/association/approval.py +
 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
class Approval:
+    @staticmethod
+    def run(
+        benchmark_uid: int,
+        approval_status: str,
+        dataset_uid: int = None,
+        mlcube_uid: int = None,
+    ):
+        """Sets approval status for an association between a benchmark and a dataset or mlcube
+
+        Args:
+            benchmark_uid (int): Benchmark UID.
+            approval_status (str): Desired approval status to set for the association.
+            comms (Comms): Instance of Comms interface.
+            ui (UI): Instance of UI interface.
+            dataset_uid (int, optional): Dataset UID. Defaults to None.
+            mlcube_uid (int, optional): MLCube UID. Defaults to None.
+        """
+        comms = config.comms
+        too_many_resources = dataset_uid and mlcube_uid
+        no_resource = dataset_uid is None and mlcube_uid is None
+        if no_resource or too_many_resources:
+            raise InvalidArgumentError("Must provide either a dataset or mlcube")
+
+        if dataset_uid:
+            comms.set_dataset_association_approval(
+                benchmark_uid, dataset_uid, approval_status.value
+            )
+
+        if mlcube_uid:
+            comms.set_mlcube_association_approval(
+                benchmark_uid, mlcube_uid, approval_status.value
+            )
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(benchmark_uid, approval_status, dataset_uid=None, mlcube_uid=None) + + + staticmethod + + +

+ + +
+ +

Sets approval status for an association between a benchmark and a dataset or mlcube

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

Benchmark UID.

+
+
+ required +
approval_status + str + +
+

Desired approval status to set for the association.

+
+
+ required +
comms + Comms + +
+

Instance of Comms interface.

+
+
+ required +
ui + UI + +
+

Instance of UI interface.

+
+
+ required +
dataset_uid + int + +
+

Dataset UID. Defaults to None.

+
+
+ None +
mlcube_uid + int + +
+

MLCube UID. Defaults to None.

+
+
+ None +
+ +
+ Source code in cli/medperf/commands/association/approval.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
@staticmethod
+def run(
+    benchmark_uid: int,
+    approval_status: str,
+    dataset_uid: int = None,
+    mlcube_uid: int = None,
+):
+    """Sets approval status for an association between a benchmark and a dataset or mlcube
+
+    Args:
+        benchmark_uid (int): Benchmark UID.
+        approval_status (str): Desired approval status to set for the association.
+        comms (Comms): Instance of Comms interface.
+        ui (UI): Instance of UI interface.
+        dataset_uid (int, optional): Dataset UID. Defaults to None.
+        mlcube_uid (int, optional): MLCube UID. Defaults to None.
+    """
+    comms = config.comms
+    too_many_resources = dataset_uid and mlcube_uid
+    no_resource = dataset_uid is None and mlcube_uid is None
+    if no_resource or too_many_resources:
+        raise InvalidArgumentError("Must provide either a dataset or mlcube")
+
+    if dataset_uid:
+        comms.set_dataset_association_approval(
+            benchmark_uid, dataset_uid, approval_status.value
+        )
+
+    if mlcube_uid:
+        comms.set_mlcube_association_approval(
+            benchmark_uid, mlcube_uid, approval_status.value
+        )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/association/association/index.html b/reference/commands/association/association/index.html new file mode 100644 index 000000000..3f831d881 --- /dev/null +++ b/reference/commands/association/association/index.html @@ -0,0 +1,1388 @@ + + + + + + + + + + + + + + + + + + + + Association - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Association

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+approve(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), dataset_uid=typer.Option(None, '--dataset', '-d', help='Dataset UID'), mlcube_uid=typer.Option(None, '--mlcube', '-m', help='MLCube UID')) + +

+ + +
+ +

Approves an association between a benchmark and a dataset or model mlcube

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

Benchmark UID.

+
+
+ typer.Option(..., '--benchmark', '-b', help='Benchmark UID') +
dataset_uid + int + +
+

Dataset UID.

+
+
+ typer.Option(None, '--dataset', '-d', help='Dataset UID') +
mlcube_uid + int + +
+

Model MLCube UID.

+
+
+ typer.Option(None, '--mlcube', '-m', help='MLCube UID') +
+ +
+ Source code in cli/medperf/commands/association/association.py +
26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
@app.command("approve")
+@clean_except
+def approve(
+    benchmark_uid: int = typer.Option(..., "--benchmark", "-b", help="Benchmark UID"),
+    dataset_uid: int = typer.Option(None, "--dataset", "-d", help="Dataset UID"),
+    mlcube_uid: int = typer.Option(None, "--mlcube", "-m", help="MLCube UID"),
+):
+    """Approves an association between a benchmark and a dataset or model mlcube
+
+    Args:
+        benchmark_uid (int): Benchmark UID.
+        dataset_uid (int, optional): Dataset UID.
+        mlcube_uid (int, optional): Model MLCube UID.
+    """
+    Approval.run(benchmark_uid, Status.APPROVED, dataset_uid, mlcube_uid)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+list(filter=typer.Argument(None)) + +

+ + +
+ +

Display all associations related to the current user.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filter + str + +
+

Filter associations by approval status. +Defaults to displaying all user associations.

+
+
+ typer.Argument(None) +
+ +
+ Source code in cli/medperf/commands/association/association.py +
14
+15
+16
+17
+18
+19
+20
+21
+22
+23
@app.command("ls")
+@clean_except
+def list(filter: Optional[str] = typer.Argument(None)):
+    """Display all associations related to the current user.
+
+    Args:
+        filter (str, optional): Filter associations by approval status.
+            Defaults to displaying all user associations.
+    """
+    ListAssociations.run(filter)
+
+
+
+ +
+ + +
+ + + +

+reject(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), dataset_uid=typer.Option(None, '--dataset', '-d', help='Dataset UID'), mlcube_uid=typer.Option(None, '--mlcube', '-m', help='MLCube UID')) + +

+ + +
+ +

Rejects an association between a benchmark and a dataset or model mlcube

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

Benchmark UID.

+
+
+ typer.Option(..., '--benchmark', '-b', help='Benchmark UID') +
dataset_uid + int + +
+

Dataset UID.

+
+
+ typer.Option(None, '--dataset', '-d', help='Dataset UID') +
mlcube_uid + int + +
+

Model MLCube UID.

+
+
+ typer.Option(None, '--mlcube', '-m', help='MLCube UID') +
+ +
+ Source code in cli/medperf/commands/association/association.py +
44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
@app.command("reject")
+@clean_except
+def reject(
+    benchmark_uid: int = typer.Option(..., "--benchmark", "-b", help="Benchmark UID"),
+    dataset_uid: int = typer.Option(None, "--dataset", "-d", help="Dataset UID"),
+    mlcube_uid: int = typer.Option(None, "--mlcube", "-m", help="MLCube UID"),
+):
+    """Rejects an association between a benchmark and a dataset or model mlcube
+
+    Args:
+        benchmark_uid (int): Benchmark UID.
+        dataset_uid (int, optional): Dataset UID.
+        mlcube_uid (int, optional): Model MLCube UID.
+    """
+    Approval.run(benchmark_uid, Status.REJECTED, dataset_uid, mlcube_uid)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+set_priority(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), mlcube_uid=typer.Option(..., '--mlcube', '-m', help='MLCube UID'), priority=typer.Option(..., '--priority', '-p', help='Priority, an integer')) + +

+ + +
+ +

Updates the priority of a benchmark-model association. Model priorities within +a benchmark define which models need to be executed before others when +this benchmark is run. A model with a higher priority is executed before +a model with lower priority. The order of execution of models of the same priority +is arbitrary.

+ + + +

Examples:

+ +

Assume there are three models of IDs (1,2,3), associated with a certain benchmark, +all having priority = 0. +- By setting the priority of model (2) to the value of 1, the client will make +sure that model (2) is executed before models (1,3). +- By setting the priority of model (1) to the value of -5, the client will make +sure that models (2,3) are executed before model (1).

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

Benchmark UID.

+
+
+ typer.Option(..., '--benchmark', '-b', help='Benchmark UID') +
mlcube_uid + int + +
+

Model MLCube UID.

+
+
+ typer.Option(..., '--mlcube', '-m', help='MLCube UID') +
priority + int + +
+

Priority, an integer

+
+
+ typer.Option(..., '--priority', '-p', help='Priority, an integer') +
+ +
+ Source code in cli/medperf/commands/association/association.py +
62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
@app.command("set_priority")
+@clean_except
+def set_priority(
+    benchmark_uid: int = typer.Option(..., "--benchmark", "-b", help="Benchmark UID"),
+    mlcube_uid: int = typer.Option(..., "--mlcube", "-m", help="MLCube UID"),
+    priority: int = typer.Option(..., "--priority", "-p", help="Priority, an integer"),
+):
+    """Updates the priority of a benchmark-model association. Model priorities within
+    a benchmark define which models need to be executed before others when
+    this benchmark is run. A model with a higher priority is executed before
+    a model with lower priority. The order of execution of models of the same priority
+    is arbitrary.
+    Examples:
+    Assume there are three models of IDs (1,2,3), associated with a certain benchmark,
+    all having priority = 0.
+    - By setting the priority of model (2) to the value of 1, the client will make
+    sure that model (2) is executed before models (1,3).
+    - By setting the priority of model (1) to the value of -5, the client will make
+    sure that models (2,3) are executed before model (1).
+
+    Args:
+        benchmark_uid (int): Benchmark UID.
+        mlcube_uid (int): Model MLCube UID.
+        priority (int): Priority, an integer
+    """
+    AssociationPriority.run(benchmark_uid, mlcube_uid, priority)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/association/list/index.html b/reference/commands/association/list/index.html new file mode 100644 index 000000000..580aa7e1c --- /dev/null +++ b/reference/commands/association/list/index.html @@ -0,0 +1,1164 @@ + + + + + + + + + + + + + + + + + + + + List - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

List

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ ListAssociations + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/association/list.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
class ListAssociations:
+    @staticmethod
+    def run(filter: str = None):
+        """Get Pending association requests"""
+        comms = config.comms
+        ui = config.ui
+        dset_assocs = comms.get_datasets_associations()
+        cube_assocs = comms.get_cubes_associations()
+
+        # Might be worth seeing if creating an association class that encapsulates
+        # most of the logic here is useful
+        assocs = dset_assocs + cube_assocs
+        if filter:
+            filter = filter.upper()
+            assocs = [assoc for assoc in assocs if assoc["approval_status"] == filter]
+
+        assocs_info = []
+        for assoc in assocs:
+            assoc_info = (
+                assoc.get("dataset", None),
+                assoc.get("model_mlcube", None),
+                assoc["benchmark"],
+                assoc["initiated_by"],
+                assoc["approval_status"],
+                assoc.get("priority", None),
+                # NOTE: We should find a better way to show priorities, since a priority
+                # is better shown when listing cube associations only, of a specific
+                # benchmark. Maybe this is resolved after we add a general filtering
+                # feature to list commands.
+            )
+            assocs_info.append(assoc_info)
+
+        headers = [
+            "Dataset UID",
+            "MLCube UID",
+            "Benchmark UID",
+            "Initiated by",
+            "Status",
+            "Priority",
+        ]
+        tab = tabulate(assocs_info, headers=headers)
+        ui.print(tab)
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(filter=None) + + + staticmethod + + +

+ + +
+ +

Get Pending association requests

+ +
+ Source code in cli/medperf/commands/association/list.py +
 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
@staticmethod
+def run(filter: str = None):
+    """Get Pending association requests"""
+    comms = config.comms
+    ui = config.ui
+    dset_assocs = comms.get_datasets_associations()
+    cube_assocs = comms.get_cubes_associations()
+
+    # Might be worth seeing if creating an association class that encapsulates
+    # most of the logic here is useful
+    assocs = dset_assocs + cube_assocs
+    if filter:
+        filter = filter.upper()
+        assocs = [assoc for assoc in assocs if assoc["approval_status"] == filter]
+
+    assocs_info = []
+    for assoc in assocs:
+        assoc_info = (
+            assoc.get("dataset", None),
+            assoc.get("model_mlcube", None),
+            assoc["benchmark"],
+            assoc["initiated_by"],
+            assoc["approval_status"],
+            assoc.get("priority", None),
+            # NOTE: We should find a better way to show priorities, since a priority
+            # is better shown when listing cube associations only, of a specific
+            # benchmark. Maybe this is resolved after we add a general filtering
+            # feature to list commands.
+        )
+        assocs_info.append(assoc_info)
+
+    headers = [
+        "Dataset UID",
+        "MLCube UID",
+        "Benchmark UID",
+        "Initiated by",
+        "Status",
+        "Priority",
+    ]
+    tab = tabulate(assocs_info, headers=headers)
+    ui.print(tab)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/association/priority/index.html b/reference/commands/association/priority/index.html new file mode 100644 index 000000000..9a3601a36 --- /dev/null +++ b/reference/commands/association/priority/index.html @@ -0,0 +1,1130 @@ + + + + + + + + + + + + + + + + + + + + Priority - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Priority

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ AssociationPriority + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/association/priority.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
class AssociationPriority:
+    @staticmethod
+    def run(benchmark_uid: int, mlcube_uid: int, priority: int):
+        """Sets priority for an association between a benchmark and an mlcube
+
+        Args:
+            benchmark_uid (int): Benchmark UID.
+            mlcube_uid (int): MLCube UID.
+            priority (int): priority value
+
+        """
+        associated_cubes = Benchmark.get_models_uids(benchmark_uid)
+        if mlcube_uid not in associated_cubes:
+            raise InvalidArgumentError(
+                "The given mlcube doesn't exist or is not associated with the benchmark"
+            )
+        config.comms.set_mlcube_association_priority(
+            benchmark_uid, mlcube_uid, priority
+        )
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(benchmark_uid, mlcube_uid, priority) + + + staticmethod + + +

+ + +
+ +

Sets priority for an association between a benchmark and an mlcube

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

Benchmark UID.

+
+
+ required +
mlcube_uid + int + +
+

MLCube UID.

+
+
+ required +
priority + int + +
+

priority value

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/association/priority.py +
 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
@staticmethod
+def run(benchmark_uid: int, mlcube_uid: int, priority: int):
+    """Sets priority for an association between a benchmark and an mlcube
+
+    Args:
+        benchmark_uid (int): Benchmark UID.
+        mlcube_uid (int): MLCube UID.
+        priority (int): priority value
+
+    """
+    associated_cubes = Benchmark.get_models_uids(benchmark_uid)
+    if mlcube_uid not in associated_cubes:
+        raise InvalidArgumentError(
+            "The given mlcube doesn't exist or is not associated with the benchmark"
+        )
+    config.comms.set_mlcube_association_priority(
+        benchmark_uid, mlcube_uid, priority
+    )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/auth/auth/index.html b/reference/commands/auth/auth/index.html new file mode 100644 index 000000000..a536f8c07 --- /dev/null +++ b/reference/commands/auth/auth/index.html @@ -0,0 +1,1100 @@ + + + + + + + + + + + + + + + + + + + + Auth - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Auth

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+login(email=typer.Option(None, '--email', '-e', help='The email associated with your account')) + +

+ + +
+ +

Authenticate to be able to access the MedPerf server. A verification link will +be provided and should be open in a browser to complete the login process.

+ +
+ Source code in cli/medperf/commands/auth/auth.py +
26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
@app.command("login")
+@clean_except
+def login(
+    email: str = typer.Option(
+        None, "--email", "-e", help="The email associated with your account"
+    )
+):
+    """Authenticate to be able to access the MedPerf server. A verification link will
+    be provided and should be open in a browser to complete the login process."""
+    Login.run(email)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+logout() + +

+ + +
+ +

Revoke the currently active login state.

+ +
+ Source code in cli/medperf/commands/auth/auth.py +
39
+40
+41
+42
+43
+44
@app.command("logout")
+@clean_except
+def logout():
+    """Revoke the currently active login state."""
+    Logout.run()
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+status() + +

+ + +
+ +

Shows the currently logged in user.

+ +
+ Source code in cli/medperf/commands/auth/auth.py +
47
+48
+49
+50
+51
@app.command("status")
+@clean_except
+def status():
+    """Shows the currently logged in user."""
+    Status.run()
+
+
+
+ +
+ + +
+ + + +

+synapse_login(token=typer.Option(None, '--token', '-t', help='Personal Access Token to login with')) + +

+ + +
+ +

Login to the synapse server. +Provide either a username and a password, or a token

+ +
+ Source code in cli/medperf/commands/auth/auth.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
@app.command("synapse_login")
+@clean_except
+def synapse_login(
+    token: str = typer.Option(
+        None, "--token", "-t", help="Personal Access Token to login with"
+    ),
+):
+    """Login to the synapse server.
+    Provide either a username and a password, or a token
+    """
+    SynapseLogin.run(token=token)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/auth/login/index.html b/reference/commands/auth/login/index.html new file mode 100644 index 000000000..cd58a779e --- /dev/null +++ b/reference/commands/auth/login/index.html @@ -0,0 +1,1049 @@ + + + + + + + + + + + + + + + + + + + + Login - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Login

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Login + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/auth/login.py +
16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
class Login:
+    @staticmethod
+    def run(email: str = None):
+        """Authenticate to be able to access the MedPerf server. A verification link will
+        be provided and should be open in a browser to complete the login process."""
+        raise_if_logged_in()
+        if not email:
+            email = config.ui.prompt("Please type your email: ")
+        try:
+            validate_email(email, check_deliverability=False)
+        except EmailNotValidError as e:
+            raise InvalidArgumentError(str(e))
+        config.auth.login(email)
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(email=None) + + + staticmethod + + +

+ + +
+ +

Authenticate to be able to access the MedPerf server. A verification link will +be provided and should be open in a browser to complete the login process.

+ +
+ Source code in cli/medperf/commands/auth/login.py +
17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
@staticmethod
+def run(email: str = None):
+    """Authenticate to be able to access the MedPerf server. A verification link will
+    be provided and should be open in a browser to complete the login process."""
+    raise_if_logged_in()
+    if not email:
+        email = config.ui.prompt("Please type your email: ")
+    try:
+        validate_email(email, check_deliverability=False)
+    except EmailNotValidError as e:
+        raise InvalidArgumentError(str(e))
+    config.auth.login(email)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/auth/logout/index.html b/reference/commands/auth/logout/index.html new file mode 100644 index 000000000..1424bb70e --- /dev/null +++ b/reference/commands/auth/logout/index.html @@ -0,0 +1,1020 @@ + + + + + + + + + + + + + + + + + + + + Logout - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Logout

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Logout + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/auth/logout.py +
4
+5
+6
+7
+8
+9
class Logout:
+    @staticmethod
+    def run():
+        """Revoke the currently active login state."""
+
+        config.auth.logout()
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run() + + + staticmethod + + +

+ + +
+ +

Revoke the currently active login state.

+ +
+ Source code in cli/medperf/commands/auth/logout.py +
5
+6
+7
+8
+9
@staticmethod
+def run():
+    """Revoke the currently active login state."""
+
+    config.auth.logout()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/auth/status/index.html b/reference/commands/auth/status/index.html new file mode 100644 index 000000000..3b2aaf75b --- /dev/null +++ b/reference/commands/auth/status/index.html @@ -0,0 +1,1040 @@ + + + + + + + + + + + + + + + + + + + + Status - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Status

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Status + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/auth/status.py +
 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
class Status:
+    @staticmethod
+    def run():
+        """Shows the currently logged in user."""
+        account_info = read_user_account()
+        if account_info is None:
+            config.ui.print("You are not logged in")
+            return
+
+        email = account_info["email"]
+        config.ui.print(f"Logged in user email address: {email}")
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run() + + + staticmethod + + +

+ + +
+ +

Shows the currently logged in user.

+ +
+ Source code in cli/medperf/commands/auth/status.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
@staticmethod
+def run():
+    """Shows the currently logged in user."""
+    account_info = read_user_account()
+    if account_info is None:
+        config.ui.print("You are not logged in")
+        return
+
+    email = account_info["email"]
+    config.ui.print(f"Logged in user email address: {email}")
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/auth/synapse_login/index.html b/reference/commands/auth/synapse_login/index.html new file mode 100644 index 000000000..7e5ca713a --- /dev/null +++ b/reference/commands/auth/synapse_login/index.html @@ -0,0 +1,1115 @@ + + + + + + + + + + + + + + + + + + + + Synapse login - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Synapse login

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ SynapseLogin + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/auth/synapse_login.py +
 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
class SynapseLogin:
+    @classmethod
+    def run(cls, token: str = None):
+        """Login to the Synapse server. Must be done only once."""
+        if not token:
+            msg = (
+                "Please provide your Synapse Personal Access Token (PAT). "
+                "You can generate a new PAT at "
+                "https://www.synapse.org/#!PersonalAccessTokens:0\n"
+                "Synapse PAT: "
+            )
+            token = config.ui.hidden_prompt(msg)
+        cls.login_with_token(token)
+
+    @classmethod
+    def login_with_token(cls, access_token=None):
+        """Login to the Synapse server. Must be done only once."""
+        syn = synapseclient.Synapse()
+        try:
+            syn.login(authToken=access_token)
+        except SynapseAuthenticationError as err:
+            raise CommunicationAuthenticationError("Invalid Synapse credentials") from err
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+login_with_token(access_token=None) + + + classmethod + + +

+ + +
+ +

Login to the Synapse server. Must be done only once.

+ +
+ Source code in cli/medperf/commands/auth/synapse_login.py +
21
+22
+23
+24
+25
+26
+27
+28
@classmethod
+def login_with_token(cls, access_token=None):
+    """Login to the Synapse server. Must be done only once."""
+    syn = synapseclient.Synapse()
+    try:
+        syn.login(authToken=access_token)
+    except SynapseAuthenticationError as err:
+        raise CommunicationAuthenticationError("Invalid Synapse credentials") from err
+
+
+
+ +
+ + +
+ + + +

+run(token=None) + + + classmethod + + +

+ + +
+ +

Login to the Synapse server. Must be done only once.

+ +
+ Source code in cli/medperf/commands/auth/synapse_login.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
@classmethod
+def run(cls, token: str = None):
+    """Login to the Synapse server. Must be done only once."""
+    if not token:
+        msg = (
+            "Please provide your Synapse Personal Access Token (PAT). "
+            "You can generate a new PAT at "
+            "https://www.synapse.org/#!PersonalAccessTokens:0\n"
+            "Synapse PAT: "
+        )
+        token = config.ui.hidden_prompt(msg)
+    cls.login_with_token(token)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/benchmark/associate/index.html b/reference/commands/benchmark/associate/index.html new file mode 100644 index 000000000..f1e472c66 --- /dev/null +++ b/reference/commands/benchmark/associate/index.html @@ -0,0 +1,1228 @@ + + + + + + + + + + + + + + + + + + + + Associate - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Associate

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ AssociateBenchmark + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/benchmark/associate.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
class AssociateBenchmark:
+    @classmethod
+    def run(
+        cls,
+        benchmark_uid: int,
+        model_uid: int,
+        data_uid: int,
+        approved=False,
+        no_cache=False,
+    ):
+        """Associates a dataset or model to the given benchmark
+
+        Args:
+            benchmark_uid (int): UID of benchmark to associate entities with
+            model_uid (int): UID of model to associate with benchmark
+            data_uid (int): UID of dataset to associate with benchmark
+            comms (Comms): Instance of Communications interface
+            ui (UI): Instance of UI interface
+            approved (bool): Skip approval step. Defaults to False
+        """
+        too_many_resources = data_uid and model_uid
+        no_resource = data_uid is None and model_uid is None
+        if no_resource or too_many_resources:
+            raise InvalidArgumentError("Must provide either a dataset or mlcube")
+        if model_uid is not None:
+            AssociateCube.run(
+                model_uid, benchmark_uid, approved=approved, no_cache=no_cache
+            )
+
+        if data_uid is not None:
+            AssociateDataset.run(
+                data_uid, benchmark_uid, approved=approved, no_cache=no_cache
+            )
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(benchmark_uid, model_uid, data_uid, approved=False, no_cache=False) + + + classmethod + + +

+ + +
+ +

Associates a dataset or model to the given benchmark

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

UID of benchmark to associate entities with

+
+
+ required +
model_uid + int + +
+

UID of model to associate with benchmark

+
+
+ required +
data_uid + int + +
+

UID of dataset to associate with benchmark

+
+
+ required +
comms + Comms + +
+

Instance of Communications interface

+
+
+ required +
ui + UI + +
+

Instance of UI interface

+
+
+ required +
approved + bool + +
+

Skip approval step. Defaults to False

+
+
+ False +
+ +
+ Source code in cli/medperf/commands/benchmark/associate.py +
 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
@classmethod
+def run(
+    cls,
+    benchmark_uid: int,
+    model_uid: int,
+    data_uid: int,
+    approved=False,
+    no_cache=False,
+):
+    """Associates a dataset or model to the given benchmark
+
+    Args:
+        benchmark_uid (int): UID of benchmark to associate entities with
+        model_uid (int): UID of model to associate with benchmark
+        data_uid (int): UID of dataset to associate with benchmark
+        comms (Comms): Instance of Communications interface
+        ui (UI): Instance of UI interface
+        approved (bool): Skip approval step. Defaults to False
+    """
+    too_many_resources = data_uid and model_uid
+    no_resource = data_uid is None and model_uid is None
+    if no_resource or too_many_resources:
+        raise InvalidArgumentError("Must provide either a dataset or mlcube")
+    if model_uid is not None:
+        AssociateCube.run(
+            model_uid, benchmark_uid, approved=approved, no_cache=no_cache
+        )
+
+    if data_uid is not None:
+        AssociateDataset.run(
+            data_uid, benchmark_uid, approved=approved, no_cache=no_cache
+        )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/benchmark/benchmark/index.html b/reference/commands/benchmark/benchmark/index.html new file mode 100644 index 000000000..a217e19c9 --- /dev/null +++ b/reference/commands/benchmark/benchmark/index.html @@ -0,0 +1,1379 @@ + + + + + + + + + + + + + + + + + + + + Benchmark - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Benchmark

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+associate(benchmark_uid=typer.Option(..., '--benchmark_uid', '-b', help='UID of benchmark to associate with'), model_uid=typer.Option(None, '--model_uid', '-m', help='UID of model MLCube to associate'), dataset_uid=typer.Option(None, '--data_uid', '-d', help='Server UID of registered dataset to associate'), approval=typer.Option(False, '-y', help='Skip approval step'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist')) + +

+ + +
+ +

Associates a benchmark with a given mlcube or dataset. Only one option at a time.

+ +
+ Source code in cli/medperf/commands/benchmark/benchmark.py +
@app.command("associate")
+@clean_except
+def associate(
+    benchmark_uid: int = typer.Option(
+        ..., "--benchmark_uid", "-b", help="UID of benchmark to associate with"
+    ),
+    model_uid: int = typer.Option(
+        None, "--model_uid", "-m", help="UID of model MLCube to associate"
+    ),
+    dataset_uid: int = typer.Option(
+        None, "--data_uid", "-d", help="Server UID of registered dataset to associate"
+    ),
+    approval: bool = typer.Option(False, "-y", help="Skip approval step"),
+    no_cache: bool = typer.Option(
+        False,
+        "--no-cache",
+        help="Execute the test even if results already exist",
+    ),
+):
+    """Associates a benchmark with a given mlcube or dataset. Only one option at a time."""
+    AssociateBenchmark.run(
+        benchmark_uid, model_uid, dataset_uid, approved=approval, no_cache=no_cache
+    )
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+list(local=typer.Option(False, '--local', help='Get local benchmarks'), mine=typer.Option(False, '--mine', help='Get current-user benchmarks')) + +

+ + +
+ +

List benchmarks stored locally and remotely from the user

+ +
+ Source code in cli/medperf/commands/benchmark/benchmark.py +
16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
@app.command("ls")
+@clean_except
+def list(
+    local: bool = typer.Option(False, "--local", help="Get local benchmarks"),
+    mine: bool = typer.Option(False, "--mine", help="Get current-user benchmarks"),
+):
+    """List benchmarks stored locally and remotely from the user"""
+    EntityList.run(
+        Benchmark,
+        fields=["UID", "Name", "Description", "State", "Approval Status", "Registered"],
+        local_only=local,
+        mine_only=mine,
+    )
+
+
+
+ +
+ + +
+ + + +

+run(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='UID of the desired benchmark'), data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), file=typer.Option(None, '--models-from-file', '-f', help='A file containing the model UIDs to be executed.\n\n The file should contain a single line as a list of\n\n comma-separated integers corresponding to the model UIDs'), ignore_model_errors=typer.Option(False, '--ignore-model-errors', help='Ignore failing model cubes, allowing for possibly submitting partial results'), no_cache=typer.Option(False, '--no-cache', help='Execute even if results already exist')) + +

+ + +
+ +

Runs the benchmark execution step for a given benchmark, prepared dataset and model

+ +
+ Source code in cli/medperf/commands/benchmark/benchmark.py +
@app.command("run")
+@clean_except
+def run(
+    benchmark_uid: int = typer.Option(
+        ..., "--benchmark", "-b", help="UID of the desired benchmark"
+    ),
+    data_uid: int = typer.Option(
+        ..., "--data_uid", "-d", help="Registered Dataset UID"
+    ),
+    file: str = typer.Option(
+        None,
+        "--models-from-file",
+        "-f",
+        help="""A file containing the model UIDs to be executed.\n
+        The file should contain a single line as a list of\n
+        comma-separated integers corresponding to the model UIDs""",
+    ),
+    ignore_model_errors: bool = typer.Option(
+        False,
+        "--ignore-model-errors",
+        help="Ignore failing model cubes, allowing for possibly submitting partial results",
+    ),
+    no_cache: bool = typer.Option(
+        False,
+        "--no-cache",
+        help="Execute even if results already exist",
+    ),
+):
+    """Runs the benchmark execution step for a given benchmark, prepared dataset and model"""
+    BenchmarkExecution.run(
+        benchmark_uid,
+        data_uid,
+        models_uids=None,
+        no_cache=no_cache,
+        models_input_file=file,
+        ignore_model_errors=ignore_model_errors,
+        show_summary=True,
+        ignore_failed_experiments=True,
+    )
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+submit(name=typer.Option(..., '--name', '-n', help='Name of the benchmark'), description=typer.Option(..., '--description', '-d', help='Description of the benchmark'), docs_url=typer.Option('', '--docs-url', '-u', help='URL to documentation'), demo_url=typer.Option(..., '--demo-url', help='Identifier to download the demonstration dataset tarball file.\n\n See `medperf mlcube submit --help` for more information'), demo_hash=typer.Option('', '--demo-hash', help='Hash of demonstration dataset tarball file'), data_preparation_mlcube=typer.Option(..., '--data-preparation-mlcube', '-p', help='Data Preparation MLCube UID'), reference_model_mlcube=typer.Option(..., '--reference-model-mlcube', '-m', help='Reference Model MLCube UID'), evaluator_mlcube=typer.Option(..., '--evaluator-mlcube', '-e', help='Evaluator MLCube UID'), skip_data_preparation_step=typer.Option(False, '--skip-demo-data-preparation', help='Use this flag if the demo dataset is already prepared'), operational=typer.Option(False, '--operational', help='Submit the Benchmark as OPERATIONAL')) + +

+ + +
+ +

Submits a new benchmark to the platform

+ +
+ Source code in cli/medperf/commands/benchmark/benchmark.py +
31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
@app.command("submit")
+@clean_except
+def submit(
+    name: str = typer.Option(..., "--name", "-n", help="Name of the benchmark"),
+    description: str = typer.Option(
+        ..., "--description", "-d", help="Description of the benchmark"
+    ),
+    docs_url: str = typer.Option("", "--docs-url", "-u", help="URL to documentation"),
+    demo_url: str = typer.Option(
+        ...,
+        "--demo-url",
+        help="""Identifier to download the demonstration dataset tarball file.\n
+        See `medperf mlcube submit --help` for more information""",
+    ),
+    demo_hash: str = typer.Option(
+        "", "--demo-hash", help="Hash of demonstration dataset tarball file"
+    ),
+    data_preparation_mlcube: int = typer.Option(
+        ..., "--data-preparation-mlcube", "-p", help="Data Preparation MLCube UID"
+    ),
+    reference_model_mlcube: int = typer.Option(
+        ..., "--reference-model-mlcube", "-m", help="Reference Model MLCube UID"
+    ),
+    evaluator_mlcube: int = typer.Option(
+        ..., "--evaluator-mlcube", "-e", help="Evaluator MLCube UID"
+    ),
+    skip_data_preparation_step: bool = typer.Option(
+        False,
+        "--skip-demo-data-preparation",
+        help="Use this flag if the demo dataset is already prepared",
+    ),
+    operational: bool = typer.Option(
+        False,
+        "--operational",
+        help="Submit the Benchmark as OPERATIONAL",
+    ),
+):
+    """Submits a new benchmark to the platform"""
+    benchmark_info = {
+        "name": name,
+        "description": description,
+        "docs_url": docs_url,
+        "demo_dataset_tarball_url": demo_url,
+        "demo_dataset_tarball_hash": demo_hash,
+        "data_preparation_mlcube": data_preparation_mlcube,
+        "reference_model_mlcube": reference_model_mlcube,
+        "data_evaluator_mlcube": evaluator_mlcube,
+        "state": "OPERATION" if operational else "DEVELOPMENT",
+    }
+    SubmitBenchmark.run(
+        benchmark_info,
+        skip_data_preparation_step=skip_data_preparation_step,
+    )
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+view(entity_id=typer.Argument(None, help='Benchmark ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local benchmarks if benchmark ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user benchmarks if benchmark ID is not provided'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed')) + +

+ + +
+ +

Displays the information of one or more benchmarks

+ +
+ Source code in cli/medperf/commands/benchmark/benchmark.py +
@app.command("view")
+@clean_except
+def view(
+    entity_id: Optional[int] = typer.Argument(None, help="Benchmark ID"),
+    format: str = typer.Option(
+        "yaml",
+        "-f",
+        "--format",
+        help="Format to display contents. Available formats: [yaml, json]",
+    ),
+    local: bool = typer.Option(
+        False,
+        "--local",
+        help="Display local benchmarks if benchmark ID is not provided",
+    ),
+    mine: bool = typer.Option(
+        False,
+        "--mine",
+        help="Display current-user benchmarks if benchmark ID is not provided",
+    ),
+    output: str = typer.Option(
+        None,
+        "--output",
+        "-o",
+        help="Output file to store contents. If not provided, the output will be displayed",
+    ),
+):
+    """Displays the information of one or more benchmarks"""
+    EntityView.run(entity_id, Benchmark, format, local, mine, output)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/benchmark/submit/index.html b/reference/commands/benchmark/submit/index.html new file mode 100644 index 000000000..df725354f --- /dev/null +++ b/reference/commands/benchmark/submit/index.html @@ -0,0 +1,1494 @@ + + + + + + + + + + + + + + + + + + + + Submit - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Submit

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ SubmitBenchmark + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/benchmark/submit.py +
class SubmitBenchmark:
+    @classmethod
+    def run(
+        cls,
+        benchmark_info: dict,
+        no_cache: bool = True,
+        skip_data_preparation_step: bool = False,
+    ):
+        """Submits a new cube to the medperf platform
+        Args:
+            benchmark_info (dict): benchmark information
+                expected keys:
+                    name (str): benchmark name
+                    description (str): benchmark description
+                    docs_url (str): benchmark documentation url
+                    demo_url (str): benchmark demo dataset url
+                    demo_hash (str): benchmark demo dataset hash
+                    data_preparation_mlcube (int): benchmark data preparation mlcube uid
+                    reference_model_mlcube (int): benchmark reference model mlcube uid
+                    evaluator_mlcube (int): benchmark data evaluator mlcube uid
+        """
+        ui = config.ui
+        submission = cls(benchmark_info, no_cache, skip_data_preparation_step)
+
+        with ui.interactive():
+            ui.text = "Getting additional information"
+            submission.get_extra_information()
+            ui.print("> Completed benchmark registration information")
+            ui.text = "Submitting Benchmark to MedPerf"
+            updated_benchmark_body = submission.submit()
+        ui.print("Uploaded")
+        submission.to_permanent_path(updated_benchmark_body)
+        submission.write(updated_benchmark_body)
+
+    def __init__(
+        self,
+        benchmark_info: dict,
+        no_cache: bool = True,
+        skip_data_preparation_step: bool = False,
+    ):
+        self.ui = config.ui
+        self.bmk = Benchmark(**benchmark_info)
+        self.no_cache = no_cache
+        self.skip_data_preparation_step = skip_data_preparation_step
+        self.bmk.metadata["demo_dataset_already_prepared"] = skip_data_preparation_step
+        config.tmp_paths.append(self.bmk.path)
+
+    def get_extra_information(self):
+        """Retrieves information that must be populated automatically,
+        like hash, generated uid and test results
+        """
+        bmk_demo_url = self.bmk.demo_dataset_tarball_url
+        bmk_demo_hash = self.bmk.demo_dataset_tarball_hash
+        try:
+            _, demo_hash = resources.get_benchmark_demo_dataset(
+                bmk_demo_url, bmk_demo_hash
+            )
+        except InvalidEntityError as e:
+            raise InvalidEntityError(f"Demo dataset {bmk_demo_url}: {e}")
+        self.bmk.demo_dataset_tarball_hash = demo_hash
+        demo_uid, results = self.run_compatibility_test()
+        self.bmk.demo_dataset_generated_uid = demo_uid
+        self.bmk.metadata["results"] = results
+
+    def run_compatibility_test(self):
+        """Runs a compatibility test to ensure elements are compatible,
+        and to extract additional information required for submission
+        """
+        self.ui.print("Running compatibility test")
+        self.bmk.write()
+        data_uid, results = CompatibilityTestExecution.run(
+            benchmark=self.bmk.generated_uid,
+            no_cache=self.no_cache,
+            skip_data_preparation_step=self.skip_data_preparation_step,
+        )
+
+        return data_uid, results
+
+    def submit(self):
+        updated_body = self.bmk.upload()
+        return updated_body
+
+    def to_permanent_path(self, bmk_dict: dict):
+        """Renames the temporary benchmark submission to a permanent one
+
+        Args:
+            bmk_dict (dict): dictionary containing updated information of the submitted benchmark
+        """
+        old_bmk_loc = self.bmk.path
+        updated_bmk = Benchmark(**bmk_dict)
+        new_bmk_loc = updated_bmk.path
+        remove_path(new_bmk_loc)
+        os.rename(old_bmk_loc, new_bmk_loc)
+
+    def write(self, updated_body):
+        bmk = Benchmark(**updated_body)
+        bmk.write()
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+get_extra_information() + +

+ + +
+ +

Retrieves information that must be populated automatically, +like hash, generated uid and test results

+ +
+ Source code in cli/medperf/commands/benchmark/submit.py +
58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
def get_extra_information(self):
+    """Retrieves information that must be populated automatically,
+    like hash, generated uid and test results
+    """
+    bmk_demo_url = self.bmk.demo_dataset_tarball_url
+    bmk_demo_hash = self.bmk.demo_dataset_tarball_hash
+    try:
+        _, demo_hash = resources.get_benchmark_demo_dataset(
+            bmk_demo_url, bmk_demo_hash
+        )
+    except InvalidEntityError as e:
+        raise InvalidEntityError(f"Demo dataset {bmk_demo_url}: {e}")
+    self.bmk.demo_dataset_tarball_hash = demo_hash
+    demo_uid, results = self.run_compatibility_test()
+    self.bmk.demo_dataset_generated_uid = demo_uid
+    self.bmk.metadata["results"] = results
+
+
+
+ +
+ + +
+ + + +

+run(benchmark_info, no_cache=True, skip_data_preparation_step=False) + + + classmethod + + +

+ + +
+ +

Submits a new cube to the medperf platform

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_info + dict + +
+

benchmark information +expected keys: + name (str): benchmark name + description (str): benchmark description + docs_url (str): benchmark documentation url + demo_url (str): benchmark demo dataset url + demo_hash (str): benchmark demo dataset hash + data_preparation_mlcube (int): benchmark data preparation mlcube uid + reference_model_mlcube (int): benchmark reference model mlcube uid + evaluator_mlcube (int): benchmark data evaluator mlcube uid

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/benchmark/submit.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
@classmethod
+def run(
+    cls,
+    benchmark_info: dict,
+    no_cache: bool = True,
+    skip_data_preparation_step: bool = False,
+):
+    """Submits a new cube to the medperf platform
+    Args:
+        benchmark_info (dict): benchmark information
+            expected keys:
+                name (str): benchmark name
+                description (str): benchmark description
+                docs_url (str): benchmark documentation url
+                demo_url (str): benchmark demo dataset url
+                demo_hash (str): benchmark demo dataset hash
+                data_preparation_mlcube (int): benchmark data preparation mlcube uid
+                reference_model_mlcube (int): benchmark reference model mlcube uid
+                evaluator_mlcube (int): benchmark data evaluator mlcube uid
+    """
+    ui = config.ui
+    submission = cls(benchmark_info, no_cache, skip_data_preparation_step)
+
+    with ui.interactive():
+        ui.text = "Getting additional information"
+        submission.get_extra_information()
+        ui.print("> Completed benchmark registration information")
+        ui.text = "Submitting Benchmark to MedPerf"
+        updated_benchmark_body = submission.submit()
+    ui.print("Uploaded")
+    submission.to_permanent_path(updated_benchmark_body)
+    submission.write(updated_benchmark_body)
+
+
+
+ +
+ + +
+ + + +

+run_compatibility_test() + +

+ + +
+ +

Runs a compatibility test to ensure elements are compatible, +and to extract additional information required for submission

+ +
+ Source code in cli/medperf/commands/benchmark/submit.py +
75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
def run_compatibility_test(self):
+    """Runs a compatibility test to ensure elements are compatible,
+    and to extract additional information required for submission
+    """
+    self.ui.print("Running compatibility test")
+    self.bmk.write()
+    data_uid, results = CompatibilityTestExecution.run(
+        benchmark=self.bmk.generated_uid,
+        no_cache=self.no_cache,
+        skip_data_preparation_step=self.skip_data_preparation_step,
+    )
+
+    return data_uid, results
+
+
+
+ +
+ + +
+ + + +

+to_permanent_path(bmk_dict) + +

+ + +
+ +

Renames the temporary benchmark submission to a permanent one

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
bmk_dict + dict + +
+

dictionary containing updated information of the submitted benchmark

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/benchmark/submit.py +
def to_permanent_path(self, bmk_dict: dict):
+    """Renames the temporary benchmark submission to a permanent one
+
+    Args:
+        bmk_dict (dict): dictionary containing updated information of the submitted benchmark
+    """
+    old_bmk_loc = self.bmk.path
+    updated_bmk = Benchmark(**bmk_dict)
+    new_bmk_loc = updated_bmk.path
+    remove_path(new_bmk_loc)
+    os.rename(old_bmk_loc, new_bmk_loc)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/compatibility_test/compatibility_test/index.html b/reference/commands/compatibility_test/compatibility_test/index.html new file mode 100644 index 000000000..fa5c040c3 --- /dev/null +++ b/reference/commands/compatibility_test/compatibility_test/index.html @@ -0,0 +1,1206 @@ + + + + + + + + + + + + + + + + + + + + Compatibility test - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Compatibility test

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+list() + +

+ + +
+ +

List previously executed tests reports.

+ +
+ Source code in cli/medperf/commands/compatibility_test/compatibility_test.py +
94
+95
+96
+97
+98
@app.command("ls")
+@clean_except
+def list():
+    """List previously executed tests reports."""
+    EntityList.run(TestReport, fields=["UID", "Data Source", "Model", "Evaluator"])
+
+
+
+ +
+ + +
+ + + +

+run(benchmark_uid=typer.Option(None, '--benchmark', '-b', help='UID of the benchmark to test. Optional'), data_uid=typer.Option(None, '--data_uid', '-d', help='Prepared Dataset UID. Used for dataset testing. Optional. Defaults to benchmark demo dataset.'), demo_dataset_url=typer.Option(None, '--demo_dataset_url', help='Identifier to download the demonstration dataset tarball file.\n\n See `medperf mlcube submit --help` for more information'), demo_dataset_hash=typer.Option(None, '--demo_dataset_hash', help='Hash of the demo dataset, if provided.'), data_path=typer.Option(None, '--data_path', help='Path to raw input data.'), labels_path=typer.Option(None, '--labels_path', help='Path to the labels of the raw input data, if provided.'), data_prep=typer.Option(None, '--data_preparation', '-p', help='UID or local path to the data preparation mlcube. Optional. Defaults to benchmark data preparation mlcube.'), model=typer.Option(None, '--model', '-m', help='UID or local path to the model mlcube. Optional. Defaults to benchmark reference mlcube.'), evaluator=typer.Option(None, '--evaluator', '-e', help='UID or local path to the evaluator mlcube. Optional. Defaults to benchmark evaluator mlcube'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist'), offline=typer.Option(False, '--offline', help='Execute the test without connecting to the MedPerf server.'), skip_data_preparation_step=typer.Option(False, '--skip-demo-data-preparation', help='Use this flag if the passed demo dataset or data path is already prepared')) + +

+ + +
+ +

Executes a compatibility test for a determined benchmark. +Can test prepared and unprepared datasets, remote and local models independently.

+ +
+ Source code in cli/medperf/commands/compatibility_test/compatibility_test.py +
14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
@app.command("run")
+@clean_except
+def run(
+    benchmark_uid: int = typer.Option(
+        None, "--benchmark", "-b", help="UID of the benchmark to test. Optional"
+    ),
+    data_uid: str = typer.Option(
+        None,
+        "--data_uid",
+        "-d",
+        help="Prepared Dataset UID. Used for dataset testing. Optional. Defaults to benchmark demo dataset.",
+    ),
+    demo_dataset_url: str = typer.Option(
+        None,
+        "--demo_dataset_url",
+        help="""Identifier to download the demonstration dataset tarball file.\n
+            See `medperf mlcube submit --help` for more information""",
+    ),
+    demo_dataset_hash: str = typer.Option(
+        None, "--demo_dataset_hash", help="Hash of the demo dataset, if provided."
+    ),
+    data_path: str = typer.Option(None, "--data_path", help="Path to raw input data."),
+    labels_path: str = typer.Option(
+        None,
+        "--labels_path",
+        help="Path to the labels of the raw input data, if provided.",
+    ),
+    data_prep: str = typer.Option(
+        None,
+        "--data_preparation",
+        "-p",
+        help="UID or local path to the data preparation mlcube. Optional. Defaults to benchmark data preparation mlcube.",
+    ),
+    model: str = typer.Option(
+        None,
+        "--model",
+        "-m",
+        help="UID or local path to the model mlcube. Optional. Defaults to benchmark reference mlcube.",
+    ),
+    evaluator: str = typer.Option(
+        None,
+        "--evaluator",
+        "-e",
+        help="UID or local path to the evaluator mlcube. Optional. Defaults to benchmark evaluator mlcube",
+    ),
+    no_cache: bool = typer.Option(
+        False, "--no-cache", help="Execute the test even if results already exist"
+    ),
+    offline: bool = typer.Option(
+        False,
+        "--offline",
+        help="Execute the test without connecting to the MedPerf server.",
+    ),
+    skip_data_preparation_step: bool = typer.Option(
+        False,
+        "--skip-demo-data-preparation",
+        help="Use this flag if the passed demo dataset or data path is already prepared",
+    ),
+):
+    """
+    Executes a compatibility test for a determined benchmark.
+    Can test prepared and unprepared datasets, remote and local models independently.
+    """
+    CompatibilityTestExecution.run(
+        benchmark_uid,
+        data_prep,
+        model,
+        evaluator,
+        data_path,
+        labels_path,
+        demo_dataset_url,
+        demo_dataset_hash,
+        data_uid,
+        no_cache=no_cache,
+        offline=offline,
+        skip_data_preparation_step=skip_data_preparation_step,
+    )
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+view(entity_id=typer.Argument(None, help='Test report ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed')) + +

+ + +
+ +

Displays the information of one or more test reports

+ +
+ Source code in cli/medperf/commands/compatibility_test/compatibility_test.py +
@app.command("view")
+@clean_except
+def view(
+    entity_id: Optional[str] = typer.Argument(None, help="Test report ID"),
+    format: str = typer.Option(
+        "yaml",
+        "-f",
+        "--format",
+        help="Format to display contents. Available formats: [yaml, json]",
+    ),
+    output: str = typer.Option(
+        None,
+        "--output",
+        "-o",
+        help="Output file to store contents. If not provided, the output will be displayed",
+    ),
+):
+    """Displays the information of one or more test reports"""
+    EntityView.run(entity_id, TestReport, format, output=output)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/compatibility_test/run/index.html b/reference/commands/compatibility_test/run/index.html new file mode 100644 index 000000000..f678dddca --- /dev/null +++ b/reference/commands/compatibility_test/run/index.html @@ -0,0 +1,2431 @@ + + + + + + + + + + + + + + + + + + + + Run - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Run

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ CompatibilityTestExecution + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
class CompatibilityTestExecution:
+    @classmethod
+    def run(
+        cls,
+        benchmark: int = None,
+        data_prep: str = None,
+        model: str = None,
+        evaluator: str = None,
+        data_path: str = None,
+        labels_path: str = None,
+        demo_dataset_url: str = None,
+        demo_dataset_hash: str = None,
+        data_uid: str = None,
+        no_cache: bool = False,
+        offline: bool = False,
+        skip_data_preparation_step: bool = False,
+    ) -> (str, dict):
+        """Execute a test workflow. Components of a complete workflow should be passed.
+        When only the benchmark is provided, it implies the following workflow will be used:
+        - the benchmark's demo dataset is used as the raw data
+        - the benchmark's data preparation cube is used
+        - the benchmark's reference model cube is used
+        - the benchmark's metrics cube is used
+
+        Overriding benchmark's components:
+        - The data prepration, model, and metrics cubes can be overriden by specifying a cube either
+        as an integer (registered) or a path (local). The path can refer either to the mlcube config
+        file or to the mlcube directory containing the mlcube config file.
+        - Instead of using the demo dataset of the benchmark, The input raw data can be overriden by providing:
+            - a demo dataset url and its hash
+            - data path and labels path
+        - A prepared dataset can be directly used. In this case the data preparator cube is never used.
+        The prepared data can be provided by either specifying an integer (registered) or a hash of a
+        locally prepared dataset.
+
+        Whether the benchmark is provided or not, the command will fail either if the user fails to
+        provide a valid complete workflow, or if the user provided extra redundant parameters.
+
+
+        Args:
+            benchmark (int, optional): Benchmark to run the test workflow for
+            data_prep (str, optional): data preparation mlcube uid or local path.
+            model (str, optional): model mlcube uid or local path.
+            evaluator (str, optional): evaluator mlcube uid or local path.
+            data_path (str, optional): path to a local raw data
+            labels_path (str, optional): path to the labels of the local raw data
+            demo_dataset_url (str, optional): Identifier to download the demonstration dataset tarball file.\n
+            See `medperf mlcube submit --help` for more information
+            demo_dataset_hash (str, optional): The hash of the demo dataset tarball file
+            data_uid (str, optional): A prepared dataset UID
+            no_cache (bool): Whether to ignore cached results of the test execution. Defaults to False.
+            offline (bool): Whether to disable communication to the MedPerf server and rely only on
+            local copies of the server assets. Defaults to False.
+
+        Returns:
+            (str): Prepared Dataset UID used for the test. Could be the one provided or a generated one.
+            (dict): Results generated by the test.
+        """
+        logging.info("Starting test execution")
+        test_exec = cls(
+            benchmark,
+            data_prep,
+            model,
+            evaluator,
+            data_path,
+            labels_path,
+            demo_dataset_url,
+            demo_dataset_hash,
+            data_uid,
+            no_cache,
+            offline,
+            skip_data_preparation_step,
+        )
+        test_exec.validate()
+        test_exec.set_data_source()
+        test_exec.process_benchmark()
+        test_exec.prepare_cubes()
+        test_exec.prepare_dataset()
+        test_exec.initialize_report()
+        results = test_exec.cached_results()
+        if results is None:
+            results = test_exec.execute()
+            test_exec.write(results)
+        else:
+            logging.info("Existing results are found. Test would not be re-executed.")
+            logging.debug(f"Existing results: {results}")
+        return test_exec.data_uid, results
+
+    def __init__(
+        self,
+        benchmark: int = None,
+        data_prep: str = None,
+        model: str = None,
+        evaluator: str = None,
+        data_path: str = None,
+        labels_path: str = None,
+        demo_dataset_url: str = None,
+        demo_dataset_hash: str = None,
+        data_uid: str = None,
+        no_cache: bool = False,
+        offline: bool = False,
+        skip_data_preparation_step: bool = False,
+    ):
+        self.benchmark_uid = benchmark
+        self.data_prep = data_prep
+        self.model = model
+        self.evaluator = evaluator
+        self.data_path = data_path
+        self.labels_path = labels_path
+        self.demo_dataset_url = demo_dataset_url
+        self.demo_dataset_hash = demo_dataset_hash
+        self.data_uid = data_uid
+        self.no_cache = no_cache
+        self.offline = offline
+        self.skip_data_preparation_step = skip_data_preparation_step
+
+        # This property will be set to either "path", "demo", "prepared", or "benchmark"
+        self.data_source = None
+
+        self.dataset = None
+        self.model_cube = None
+        self.evaluator_cube = None
+
+        self.validator = CompatibilityTestParamsValidator(
+            self.benchmark_uid,
+            self.data_prep,
+            self.model,
+            self.evaluator,
+            self.data_path,
+            self.labels_path,
+            self.demo_dataset_url,
+            self.demo_dataset_hash,
+            self.data_uid,
+        )
+
+    def validate(self):
+        self.validator.validate()
+
+    def set_data_source(self):
+        self.data_source = self.validator.get_data_source()
+
+    def process_benchmark(self):
+        """Process the benchmark input if given. Sets the needed parameters from
+        the benchmark."""
+        if not self.benchmark_uid:
+            return
+
+        benchmark = Benchmark.get(self.benchmark_uid, local_only=self.offline)
+        if self.data_source != "prepared":
+            self.data_prep = self.data_prep or benchmark.data_preparation_mlcube
+        self.model = self.model or benchmark.reference_model_mlcube
+        self.evaluator = self.evaluator or benchmark.data_evaluator_mlcube
+        if self.data_source == "benchmark":
+            self.demo_dataset_url = benchmark.demo_dataset_tarball_url
+            self.demo_dataset_hash = benchmark.demo_dataset_tarball_hash
+            self.skip_data_preparation_step = benchmark.metadata.get(
+                "demo_dataset_already_prepared", False
+            )
+
+    def prepare_cubes(self):
+        """Prepares the mlcubes. If the provided mlcube is a path, it will create
+        a temporary uid and link the cube path to the medperf storage path."""
+
+        if self.data_source != "prepared":
+            logging.info(f"Establishing the data preparation cube: {self.data_prep}")
+            self.data_prep = prepare_cube(self.data_prep)
+
+        logging.info(f"Establishing the model cube: {self.model}")
+        self.model = prepare_cube(self.model)
+        logging.info(f"Establishing the evaluator cube: {self.evaluator}")
+        self.evaluator = prepare_cube(self.evaluator)
+
+        self.model_cube = get_cube(self.model, "Model", local_only=self.offline)
+        self.evaluator_cube = get_cube(
+            self.evaluator, "Evaluator", local_only=self.offline
+        )
+
+    def prepare_dataset(self):
+        """Assigns the data_uid used for testing and retrieves the dataset.
+        If the data is not prepared, it calls the data preparation step
+        on the given local data path or using a remote demo dataset."""
+
+        logging.info("Establishing data_uid for test execution")
+        if self.data_source != "prepared":
+            if self.data_source == "path":
+                data_path, labels_path = self.data_path, self.labels_path
+                # TODO: this has to be redesigned. Compatibility tests command
+                #       is starting to have a lot of input arguments. For now
+                #       let's not support accepting a metadata path
+                metadata_path = None
+            else:
+                data_path, labels_path, metadata_path = download_demo_data(
+                    self.demo_dataset_url, self.demo_dataset_hash
+                )
+
+            self.data_uid = create_test_dataset(
+                data_path,
+                labels_path,
+                metadata_path,
+                self.data_prep,
+                self.skip_data_preparation_step,
+            )
+
+        self.dataset = Dataset.get(self.data_uid, local_only=self.offline)
+
+    def initialize_report(self):
+        """Initializes an instance of `TestReport` to hold the current test information."""
+
+        report_data = {
+            "demo_dataset_url": self.demo_dataset_url,
+            "demo_dataset_hash": self.demo_dataset_hash,
+            "data_path": self.data_path,
+            "labels_path": self.labels_path,
+            "prepared_data_hash": self.data_uid,
+            "data_preparation_mlcube": self.data_prep,
+            "model": self.model,
+            "data_evaluator_mlcube": self.evaluator,
+        }
+        self.report = TestReport(**report_data)
+
+    def cached_results(self):
+        """checks the existance of, and retrieves if possible, the compatibility test
+        result. This method is called prior to the test execution.
+
+        Returns:
+            (dict|None): None if the results does not exist or if self.no_cache is True,
+            otherwise it returns the found results.
+        """
+        if self.no_cache:
+            return
+        uid = self.report.generated_uid
+        try:
+            report = TestReport.get(uid)
+        except InvalidArgumentError:
+            return
+        logging.info(f"Existing report {uid} was detected.")
+        logging.info("The compatibilty test will not be re-executed.")
+        return report.results
+
+    def execute(self):
+        """Runs the test execution flow and returns the results
+
+        Returns:
+            dict: returns the results of the test execution.
+        """
+        execution_summary = Execution.run(
+            dataset=self.dataset,
+            model=self.model_cube,
+            evaluator=self.evaluator_cube,
+            ignore_model_errors=False,
+        )
+        return execution_summary["results"]
+
+    def write(self, results):
+        """Writes a report of the test execution to the disk
+        Args:
+            results (dict): the results of the test execution
+        """
+        self.report.set_results(results)
+        self.report.write()
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+cached_results() + +

+ + +
+ +

checks the existance of, and retrieves if possible, the compatibility test +result. This method is called prior to the test execution.

+ + + +

Returns:

+ + + + + + + + + + + + + + + + + +
TypeDescription
+ dict | None + +
+

None if the results does not exist or if self.no_cache is True,

+
+
+ +
+

otherwise it returns the found results.

+
+
+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
def cached_results(self):
+    """checks the existance of, and retrieves if possible, the compatibility test
+    result. This method is called prior to the test execution.
+
+    Returns:
+        (dict|None): None if the results does not exist or if self.no_cache is True,
+        otherwise it returns the found results.
+    """
+    if self.no_cache:
+        return
+    uid = self.report.generated_uid
+    try:
+        report = TestReport.get(uid)
+    except InvalidArgumentError:
+        return
+    logging.info(f"Existing report {uid} was detected.")
+    logging.info("The compatibilty test will not be re-executed.")
+    return report.results
+
+
+
+ +
+ + +
+ + + +

+execute() + +

+ + +
+ +

Runs the test execution flow and returns the results

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + +
+

returns the results of the test execution.

+
+
+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
def execute(self):
+    """Runs the test execution flow and returns the results
+
+    Returns:
+        dict: returns the results of the test execution.
+    """
+    execution_summary = Execution.run(
+        dataset=self.dataset,
+        model=self.model_cube,
+        evaluator=self.evaluator_cube,
+        ignore_model_errors=False,
+    )
+    return execution_summary["results"]
+
+
+
+ +
+ + +
+ + + +

+initialize_report() + +

+ + +
+ +

Initializes an instance of TestReport to hold the current test information.

+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
def initialize_report(self):
+    """Initializes an instance of `TestReport` to hold the current test information."""
+
+    report_data = {
+        "demo_dataset_url": self.demo_dataset_url,
+        "demo_dataset_hash": self.demo_dataset_hash,
+        "data_path": self.data_path,
+        "labels_path": self.labels_path,
+        "prepared_data_hash": self.data_uid,
+        "data_preparation_mlcube": self.data_prep,
+        "model": self.model,
+        "data_evaluator_mlcube": self.evaluator,
+    }
+    self.report = TestReport(**report_data)
+
+
+
+ +
+ + +
+ + + +

+prepare_cubes() + +

+ + +
+ +

Prepares the mlcubes. If the provided mlcube is a path, it will create +a temporary uid and link the cube path to the medperf storage path.

+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
def prepare_cubes(self):
+    """Prepares the mlcubes. If the provided mlcube is a path, it will create
+    a temporary uid and link the cube path to the medperf storage path."""
+
+    if self.data_source != "prepared":
+        logging.info(f"Establishing the data preparation cube: {self.data_prep}")
+        self.data_prep = prepare_cube(self.data_prep)
+
+    logging.info(f"Establishing the model cube: {self.model}")
+    self.model = prepare_cube(self.model)
+    logging.info(f"Establishing the evaluator cube: {self.evaluator}")
+    self.evaluator = prepare_cube(self.evaluator)
+
+    self.model_cube = get_cube(self.model, "Model", local_only=self.offline)
+    self.evaluator_cube = get_cube(
+        self.evaluator, "Evaluator", local_only=self.offline
+    )
+
+
+
+ +
+ + +
+ + + +

+prepare_dataset() + +

+ + +
+ +

Assigns the data_uid used for testing and retrieves the dataset. +If the data is not prepared, it calls the data preparation step +on the given local data path or using a remote demo dataset.

+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
def prepare_dataset(self):
+    """Assigns the data_uid used for testing and retrieves the dataset.
+    If the data is not prepared, it calls the data preparation step
+    on the given local data path or using a remote demo dataset."""
+
+    logging.info("Establishing data_uid for test execution")
+    if self.data_source != "prepared":
+        if self.data_source == "path":
+            data_path, labels_path = self.data_path, self.labels_path
+            # TODO: this has to be redesigned. Compatibility tests command
+            #       is starting to have a lot of input arguments. For now
+            #       let's not support accepting a metadata path
+            metadata_path = None
+        else:
+            data_path, labels_path, metadata_path = download_demo_data(
+                self.demo_dataset_url, self.demo_dataset_hash
+            )
+
+        self.data_uid = create_test_dataset(
+            data_path,
+            labels_path,
+            metadata_path,
+            self.data_prep,
+            self.skip_data_preparation_step,
+        )
+
+    self.dataset = Dataset.get(self.data_uid, local_only=self.offline)
+
+
+
+ +
+ + +
+ + + +

+process_benchmark() + +

+ + +
+ +

Process the benchmark input if given. Sets the needed parameters from +the benchmark.

+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
def process_benchmark(self):
+    """Process the benchmark input if given. Sets the needed parameters from
+    the benchmark."""
+    if not self.benchmark_uid:
+        return
+
+    benchmark = Benchmark.get(self.benchmark_uid, local_only=self.offline)
+    if self.data_source != "prepared":
+        self.data_prep = self.data_prep or benchmark.data_preparation_mlcube
+    self.model = self.model or benchmark.reference_model_mlcube
+    self.evaluator = self.evaluator or benchmark.data_evaluator_mlcube
+    if self.data_source == "benchmark":
+        self.demo_dataset_url = benchmark.demo_dataset_tarball_url
+        self.demo_dataset_hash = benchmark.demo_dataset_tarball_hash
+        self.skip_data_preparation_step = benchmark.metadata.get(
+            "demo_dataset_already_prepared", False
+        )
+
+
+
+ +
+ + +
+ + + +

+run(benchmark=None, data_prep=None, model=None, evaluator=None, data_path=None, labels_path=None, demo_dataset_url=None, demo_dataset_hash=None, data_uid=None, no_cache=False, offline=False, skip_data_preparation_step=False) + + + classmethod + + +

+ + +
+ +

Execute a test workflow. Components of a complete workflow should be passed. +When only the benchmark is provided, it implies the following workflow will be used: +- the benchmark's demo dataset is used as the raw data +- the benchmark's data preparation cube is used +- the benchmark's reference model cube is used +- the benchmark's metrics cube is used

+

Overriding benchmark's components: +- The data prepration, model, and metrics cubes can be overriden by specifying a cube either +as an integer (registered) or a path (local). The path can refer either to the mlcube config +file or to the mlcube directory containing the mlcube config file. +- Instead of using the demo dataset of the benchmark, The input raw data can be overriden by providing: + - a demo dataset url and its hash + - data path and labels path +- A prepared dataset can be directly used. In this case the data preparator cube is never used. +The prepared data can be provided by either specifying an integer (registered) or a hash of a +locally prepared dataset.

+

Whether the benchmark is provided or not, the command will fail either if the user fails to +provide a valid complete workflow, or if the user provided extra redundant parameters.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark + int + +
+

Benchmark to run the test workflow for

+
+
+ None +
data_prep + str + +
+

data preparation mlcube uid or local path.

+
+
+ None +
model + str + +
+

model mlcube uid or local path.

+
+
+ None +
evaluator + str + +
+

evaluator mlcube uid or local path.

+
+
+ None +
data_path + str + +
+

path to a local raw data

+
+
+ None +
labels_path + str + +
+

path to the labels of the local raw data

+
+
+ None +
demo_dataset_url + str + +
+

Identifier to download the demonstration dataset tarball file.

+
+
+ None +
demo_dataset_hash + str + +
+

The hash of the demo dataset tarball file

+
+
+ None +
data_uid + str + +
+

A prepared dataset UID

+
+
+ None +
no_cache + bool + +
+

Whether to ignore cached results of the test execution. Defaults to False.

+
+
+ False +
offline + bool + +
+

Whether to disable communication to the MedPerf server and rely only on

+
+
+ False +
+ + + +

Returns:

+ + + + + + + + + + + + + + + + + +
TypeDescription
+ str + +
+

Prepared Dataset UID used for the test. Could be the one provided or a generated one.

+
+
+ dict + +
+

Results generated by the test.

+
+
+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
@classmethod
+def run(
+    cls,
+    benchmark: int = None,
+    data_prep: str = None,
+    model: str = None,
+    evaluator: str = None,
+    data_path: str = None,
+    labels_path: str = None,
+    demo_dataset_url: str = None,
+    demo_dataset_hash: str = None,
+    data_uid: str = None,
+    no_cache: bool = False,
+    offline: bool = False,
+    skip_data_preparation_step: bool = False,
+) -> (str, dict):
+    """Execute a test workflow. Components of a complete workflow should be passed.
+    When only the benchmark is provided, it implies the following workflow will be used:
+    - the benchmark's demo dataset is used as the raw data
+    - the benchmark's data preparation cube is used
+    - the benchmark's reference model cube is used
+    - the benchmark's metrics cube is used
+
+    Overriding benchmark's components:
+    - The data prepration, model, and metrics cubes can be overriden by specifying a cube either
+    as an integer (registered) or a path (local). The path can refer either to the mlcube config
+    file or to the mlcube directory containing the mlcube config file.
+    - Instead of using the demo dataset of the benchmark, The input raw data can be overriden by providing:
+        - a demo dataset url and its hash
+        - data path and labels path
+    - A prepared dataset can be directly used. In this case the data preparator cube is never used.
+    The prepared data can be provided by either specifying an integer (registered) or a hash of a
+    locally prepared dataset.
+
+    Whether the benchmark is provided or not, the command will fail either if the user fails to
+    provide a valid complete workflow, or if the user provided extra redundant parameters.
+
+
+    Args:
+        benchmark (int, optional): Benchmark to run the test workflow for
+        data_prep (str, optional): data preparation mlcube uid or local path.
+        model (str, optional): model mlcube uid or local path.
+        evaluator (str, optional): evaluator mlcube uid or local path.
+        data_path (str, optional): path to a local raw data
+        labels_path (str, optional): path to the labels of the local raw data
+        demo_dataset_url (str, optional): Identifier to download the demonstration dataset tarball file.\n
+        See `medperf mlcube submit --help` for more information
+        demo_dataset_hash (str, optional): The hash of the demo dataset tarball file
+        data_uid (str, optional): A prepared dataset UID
+        no_cache (bool): Whether to ignore cached results of the test execution. Defaults to False.
+        offline (bool): Whether to disable communication to the MedPerf server and rely only on
+        local copies of the server assets. Defaults to False.
+
+    Returns:
+        (str): Prepared Dataset UID used for the test. Could be the one provided or a generated one.
+        (dict): Results generated by the test.
+    """
+    logging.info("Starting test execution")
+    test_exec = cls(
+        benchmark,
+        data_prep,
+        model,
+        evaluator,
+        data_path,
+        labels_path,
+        demo_dataset_url,
+        demo_dataset_hash,
+        data_uid,
+        no_cache,
+        offline,
+        skip_data_preparation_step,
+    )
+    test_exec.validate()
+    test_exec.set_data_source()
+    test_exec.process_benchmark()
+    test_exec.prepare_cubes()
+    test_exec.prepare_dataset()
+    test_exec.initialize_report()
+    results = test_exec.cached_results()
+    if results is None:
+        results = test_exec.execute()
+        test_exec.write(results)
+    else:
+        logging.info("Existing results are found. Test would not be re-executed.")
+        logging.debug(f"Existing results: {results}")
+    return test_exec.data_uid, results
+
+
+
+ +
+ + +
+ + + +

+write(results) + +

+ + +
+ +

Writes a report of the test execution to the disk

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
results + dict + +
+

the results of the test execution

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/compatibility_test/run.py +
def write(self, results):
+    """Writes a report of the test execution to the disk
+    Args:
+        results (dict): the results of the test execution
+    """
+    self.report.set_results(results)
+    self.report.write()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/compatibility_test/utils/index.html b/reference/commands/compatibility_test/utils/index.html new file mode 100644 index 000000000..4fdf170f9 --- /dev/null +++ b/reference/commands/compatibility_test/utils/index.html @@ -0,0 +1,1166 @@ + + + + + + + + + + + + + + + + + + + + Utils - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Utils

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+download_demo_data(dset_url, dset_hash) + +

+ + +
+ +

Retrieves the demo dataset associated to the specified benchmark

+ + + +

Returns:

+ + + + + + + + + + + + + + + + + +
Name TypeDescription
data_path + str + +
+

Location of the downloaded data

+
+
labels_path + str + +
+

Location of the downloaded labels

+
+
+ +
+ Source code in cli/medperf/commands/compatibility_test/utils.py +
15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
def download_demo_data(dset_url, dset_hash):
+    """Retrieves the demo dataset associated to the specified benchmark
+
+    Returns:
+        data_path (str): Location of the downloaded data
+        labels_path (str): Location of the downloaded labels
+    """
+    try:
+        demo_dset_path, _ = resources.get_benchmark_demo_dataset(dset_url, dset_hash)
+    except InvalidEntityError as e:
+        raise InvalidEntityError(f"Demo dataset {dset_url}: {e}")
+
+    # It is assumed that all demo datasets contain a file
+    # which specifies the input of the data preparation step
+    paths_file = os.path.join(demo_dset_path, config.demo_dset_paths_file)
+    with open(paths_file, "r") as f:
+        paths = yaml.safe_load(f)
+
+    data_path = os.path.join(demo_dset_path, paths["data_path"])
+    labels_path = os.path.join(demo_dset_path, paths["labels_path"])
+    metadata_path = None
+    if "metadata_path" in paths:
+        metadata_path = os.path.join(demo_dset_path, paths["metadata_path"])
+
+    return data_path, labels_path, metadata_path
+
+
+
+ +
+ + +
+ + + +

+prepare_cube(cube_uid) + +

+ + +
+ +

Assigns the attr used for testing according to the initialization parameters. +If the value is a path, it will create a temporary uid and link the cube path to +the medperf storage path.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
attr + str + +
+

Attribute to check and/or reassign.

+
+
+ required +
fallback + any + +
+

Value to assign if attribute is empty. Defaults to None.

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/compatibility_test/utils.py +
def prepare_cube(cube_uid: str):
+    """Assigns the attr used for testing according to the initialization parameters.
+    If the value is a path, it will create a temporary uid and link the cube path to
+    the medperf storage path.
+
+    Arguments:
+        attr (str): Attribute to check and/or reassign.
+        fallback (any): Value to assign if attribute is empty. Defaults to None.
+    """
+
+    # Test if value looks like an mlcube_uid, if so skip path validation
+    if str(cube_uid).isdigit():
+        logging.info(f"MLCube value {cube_uid} resembles an mlcube_uid")
+        return cube_uid
+
+    # Check if value is a local mlcube
+    path = Path(cube_uid)
+    if path.is_file():
+        path = path.parent
+    path = path.resolve()
+
+    if os.path.exists(path):
+        mlcube_yaml_path = os.path.join(path, config.cube_filename)
+        if os.path.exists(mlcube_yaml_path):
+            logging.info("local path provided. Creating symbolic link")
+            temp_uid = prepare_local_cube(path)
+            return temp_uid
+
+    logging.error(f"mlcube {cube_uid} was not found as an existing mlcube")
+    raise InvalidArgumentError(
+        f"The provided mlcube ({cube_uid}) could not be found as a local or remote mlcube"
+    )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/compatibility_test/validate_params/index.html b/reference/commands/compatibility_test/validate_params/index.html new file mode 100644 index 000000000..33d004690 --- /dev/null +++ b/reference/commands/compatibility_test/validate_params/index.html @@ -0,0 +1,1415 @@ + + + + + + + + + + + + + + + + + + + + Validate params - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Validate params

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ CompatibilityTestParamsValidator + + +

+ + +
+ + +

Validates the input parameters to the CompatibilityTestExecution class

+ +
+ Source code in cli/medperf/commands/compatibility_test/validate_params.py +
  4
+  5
+  6
+  7
+  8
+  9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
class CompatibilityTestParamsValidator:
+    """Validates the input parameters to the CompatibilityTestExecution class"""
+
+    def __init__(
+        self,
+        benchmark: int = None,
+        data_prep: str = None,
+        model: str = None,
+        evaluator: str = None,
+        data_path: str = None,
+        labels_path: str = None,
+        demo_dataset_url: str = None,
+        demo_dataset_hash: str = None,
+        data_uid: str = None,
+    ):
+        self.benchmark_uid = benchmark
+        self.data_prep = data_prep
+        self.model = model
+        self.evaluator = evaluator
+        self.data_path = data_path
+        self.labels_path = labels_path
+        self.demo_dataset_url = demo_dataset_url
+        self.demo_dataset_hash = demo_dataset_hash
+        self.data_uid = data_uid
+
+    def __validate_cubes(self):
+        if not self.model and not self.benchmark_uid:
+            raise InvalidArgumentError(
+                "A model mlcube or a benchmark should at least be specified"
+            )
+
+        if not self.evaluator and not self.benchmark_uid:
+            raise InvalidArgumentError(
+                "A metrics mlcube or a benchmark should at least be specified"
+            )
+
+    def __raise_redundant_data_source(self):
+        msg = "Make sure you pass only one data source: "
+        msg += "either a prepared dataset, a data path and labels path, or a demo dataset url"
+        raise InvalidArgumentError(msg)
+
+    def __validate_prepared_data_source(self):
+        if any(
+            [
+                self.data_path,
+                self.labels_path,
+                self.demo_dataset_url,
+                self.demo_dataset_hash,
+            ]
+        ):
+            self.__raise_redundant_data_source()
+        if self.data_prep:
+            raise InvalidArgumentError(
+                "A data preparation cube is not needed when specifying a prepared dataset"
+            )
+
+    def __validate_data_path_source(self):
+        if not self.labels_path:
+            raise InvalidArgumentError(
+                "Labels path should be specified when providing data path"
+            )
+        if any([self.demo_dataset_url, self.demo_dataset_hash, self.data_uid]):
+            self.__raise_redundant_data_source()
+
+        if not self.data_prep and not self.benchmark_uid:
+            raise InvalidArgumentError(
+                "A data preparation cube should be passed when specifying raw data input"
+            )
+
+    def __validate_demo_data_source(self):
+        if not self.demo_dataset_hash:
+            raise InvalidArgumentError(
+                "The hash of the provided demo dataset should be specified"
+            )
+        if any([self.data_path, self.labels_path, self.data_uid]):
+            self.__raise_redundant_data_source()
+
+        if not self.data_prep and not self.benchmark_uid:
+            raise InvalidArgumentError(
+                "A data preparation cube should be passed when specifying raw data input"
+            )
+
+    def __validate_data_source(self):
+        if self.data_uid:
+            self.__validate_prepared_data_source()
+            return
+
+        if self.data_path:
+            self.__validate_data_path_source()
+            return
+
+        if self.demo_dataset_url:
+            self.__validate_demo_data_source()
+            return
+
+        if self.benchmark_uid:
+            return
+
+        msg = "A data source should at least be specified, either by providing"
+        msg += " a prepared data uid, a demo dataset url, data path, or a benchmark"
+        raise InvalidArgumentError(msg)
+
+    def __validate_redundant_benchmark(self):
+        if not self.benchmark_uid:
+            return
+
+        redundant_bmk_demo = any([self.data_uid, self.data_path, self.demo_dataset_url])
+        redundant_bmk_model = self.model is not None
+        redundant_bmk_evaluator = self.evaluator is not None
+        redundant_bmk_preparator = (
+            self.data_prep is not None or self.data_uid is not None
+        )
+        if all(
+            [
+                redundant_bmk_demo,
+                redundant_bmk_model,
+                redundant_bmk_evaluator,
+                redundant_bmk_preparator,
+            ]
+        ):
+            raise InvalidArgumentError("The provided benchmark will not be used")
+
+    def validate(self):
+        """Ensures test has been passed a valid combination of parameters.
+        Raises `medperf.exceptions.InvalidArgumentError` when the parameters are
+        invalid.
+        """
+
+        self.__validate_cubes()
+        self.__validate_data_source()
+        self.__validate_redundant_benchmark()
+
+    def get_data_source(self):
+        """Parses the input parameters and returns a string, one of:
+        "prepared", if the source of data is a prepared dataset uid,
+        "path", if the source of data is a local path to raw data,
+        "demo", if the source of data is a demo dataset url,
+        or "benchmark", if the source of data is the demo dataset of a benchmark.
+
+        This function assumes the passed parameters to the constructor have been already
+        validated.
+        """
+        if self.data_uid:
+            return "prepared"
+
+        if self.data_path:
+            return "path"
+
+        if self.demo_dataset_url:
+            return "demo"
+
+        if self.benchmark_uid:
+            return "benchmark"
+
+        raise MedperfException(
+            "Ensure calling the `validate` method before using this method"
+        )
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+get_data_source() + +

+ + +
+ +

Parses the input parameters and returns a string, one of: +"prepared", if the source of data is a prepared dataset uid, +"path", if the source of data is a local path to raw data, +"demo", if the source of data is a demo dataset url, +or "benchmark", if the source of data is the demo dataset of a benchmark.

+

This function assumes the passed parameters to the constructor have been already +validated.

+ +
+ Source code in cli/medperf/commands/compatibility_test/validate_params.py +
def get_data_source(self):
+    """Parses the input parameters and returns a string, one of:
+    "prepared", if the source of data is a prepared dataset uid,
+    "path", if the source of data is a local path to raw data,
+    "demo", if the source of data is a demo dataset url,
+    or "benchmark", if the source of data is the demo dataset of a benchmark.
+
+    This function assumes the passed parameters to the constructor have been already
+    validated.
+    """
+    if self.data_uid:
+        return "prepared"
+
+    if self.data_path:
+        return "path"
+
+    if self.demo_dataset_url:
+        return "demo"
+
+    if self.benchmark_uid:
+        return "benchmark"
+
+    raise MedperfException(
+        "Ensure calling the `validate` method before using this method"
+    )
+
+
+
+ +
+ + +
+ + + +

+validate() + +

+ + +
+ +

Ensures test has been passed a valid combination of parameters. +Raises medperf.exceptions.InvalidArgumentError when the parameters are +invalid.

+ +
+ Source code in cli/medperf/commands/compatibility_test/validate_params.py +
def validate(self):
+    """Ensures test has been passed a valid combination of parameters.
+    Raises `medperf.exceptions.InvalidArgumentError` when the parameters are
+    invalid.
+    """
+
+    self.__validate_cubes()
+    self.__validate_data_source()
+    self.__validate_redundant_benchmark()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/dataset/associate/index.html b/reference/commands/dataset/associate/index.html new file mode 100644 index 000000000..b377c2f8e --- /dev/null +++ b/reference/commands/dataset/associate/index.html @@ -0,0 +1,1216 @@ + + + + + + + + + + + + + + + + + + + + Associate - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Associate

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ AssociateDataset + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/dataset/associate.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
class AssociateDataset:
+    @staticmethod
+    def run(data_uid: int, benchmark_uid: int, approved=False, no_cache=False):
+        """Associates a registered dataset with a benchmark
+
+        Args:
+            data_uid (int): UID of the registered dataset to associate
+            benchmark_uid (int): UID of the benchmark to associate with
+        """
+        comms = config.comms
+        ui = config.ui
+        dset = Dataset.get(data_uid)
+        if dset.id is None:
+            msg = "The provided dataset is not registered."
+            raise InvalidArgumentError(msg)
+
+        benchmark = Benchmark.get(benchmark_uid)
+
+        if dset.data_preparation_mlcube != benchmark.data_preparation_mlcube:
+            raise InvalidArgumentError(
+                "The specified dataset wasn't prepared for this benchmark"
+            )
+
+        result = BenchmarkExecution.run(
+            benchmark_uid,
+            data_uid,
+            [benchmark.reference_model_mlcube],
+            no_cache=no_cache,
+        )[0]
+        ui.print("These are the results generated by the compatibility test. ")
+        ui.print("This will be sent along the association request.")
+        ui.print("They will not be part of the benchmark.")
+        dict_pretty_print(result.results)
+
+        msg = "Please confirm that you would like to associate"
+        msg += f" the dataset {dset.name} with the benchmark {benchmark.name}."
+        msg += " [Y/n]"
+        approved = approved or approval_prompt(msg)
+        if approved:
+            ui.print("Generating dataset benchmark association")
+            metadata = {"test_result": result.results}
+            comms.associate_dset(dset.id, benchmark_uid, metadata)
+        else:
+            ui.print("Dataset association operation cancelled.")
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(data_uid, benchmark_uid, approved=False, no_cache=False) + + + staticmethod + + +

+ + +
+ +

Associates a registered dataset with a benchmark

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
data_uid + int + +
+

UID of the registered dataset to associate

+
+
+ required +
benchmark_uid + int + +
+

UID of the benchmark to associate with

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/dataset/associate.py +
10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
@staticmethod
+def run(data_uid: int, benchmark_uid: int, approved=False, no_cache=False):
+    """Associates a registered dataset with a benchmark
+
+    Args:
+        data_uid (int): UID of the registered dataset to associate
+        benchmark_uid (int): UID of the benchmark to associate with
+    """
+    comms = config.comms
+    ui = config.ui
+    dset = Dataset.get(data_uid)
+    if dset.id is None:
+        msg = "The provided dataset is not registered."
+        raise InvalidArgumentError(msg)
+
+    benchmark = Benchmark.get(benchmark_uid)
+
+    if dset.data_preparation_mlcube != benchmark.data_preparation_mlcube:
+        raise InvalidArgumentError(
+            "The specified dataset wasn't prepared for this benchmark"
+        )
+
+    result = BenchmarkExecution.run(
+        benchmark_uid,
+        data_uid,
+        [benchmark.reference_model_mlcube],
+        no_cache=no_cache,
+    )[0]
+    ui.print("These are the results generated by the compatibility test. ")
+    ui.print("This will be sent along the association request.")
+    ui.print("They will not be part of the benchmark.")
+    dict_pretty_print(result.results)
+
+    msg = "Please confirm that you would like to associate"
+    msg += f" the dataset {dset.name} with the benchmark {benchmark.name}."
+    msg += " [Y/n]"
+    approved = approved or approval_prompt(msg)
+    if approved:
+        ui.print("Generating dataset benchmark association")
+        metadata = {"test_result": result.results}
+        comms.associate_dset(dset.id, benchmark_uid, metadata)
+    else:
+        ui.print("Dataset association operation cancelled.")
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/dataset/dataset/index.html b/reference/commands/dataset/dataset/index.html new file mode 100644 index 000000000..8356c5892 --- /dev/null +++ b/reference/commands/dataset/dataset/index.html @@ -0,0 +1,1373 @@ + + + + + + + + + + + + + + + + + + + + Dataset - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Dataset

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+associate(data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), benchmark_uid=typer.Option(..., '--benchmark_uid', '-b', help='Benchmark UID'), approval=typer.Option(False, '-y', help='Skip approval step'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist')) + +

+ + +
+ +

Associate a registered dataset with a specific benchmark. +The dataset and benchmark must share the same data preparation cube.

+ +
+ Source code in cli/medperf/commands/dataset/dataset.py +
@app.command("associate")
+@clean_except
+def associate(
+    data_uid: int = typer.Option(
+        ..., "--data_uid", "-d", help="Registered Dataset UID"
+    ),
+    benchmark_uid: int = typer.Option(
+        ..., "--benchmark_uid", "-b", help="Benchmark UID"
+    ),
+    approval: bool = typer.Option(False, "-y", help="Skip approval step"),
+    no_cache: bool = typer.Option(
+        False,
+        "--no-cache",
+        help="Execute the test even if results already exist",
+    ),
+):
+    """Associate a registered dataset with a specific benchmark.
+    The dataset and benchmark must share the same data preparation cube.
+    """
+    ui = config.ui
+    AssociateDataset.run(data_uid, benchmark_uid, approved=approval, no_cache=no_cache)
+    ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+list(local=typer.Option(False, '--local', help='Get local datasets'), mine=typer.Option(False, '--mine', help='Get current-user datasets'), mlcube=typer.Option(None, '--mlcube', '-m', help='Get datasets for a given data prep mlcube')) + +

+ + +
+ +

List datasets stored locally and remotely from the user

+ +
+ Source code in cli/medperf/commands/dataset/dataset.py +
17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
@app.command("ls")
+@clean_except
+def list(
+    local: bool = typer.Option(False, "--local", help="Get local datasets"),
+    mine: bool = typer.Option(False, "--mine", help="Get current-user datasets"),
+    mlcube: int = typer.Option(
+        None, "--mlcube", "-m", help="Get datasets for a given data prep mlcube"
+    ),
+):
+    """List datasets stored locally and remotely from the user"""
+    EntityList.run(
+        Dataset,
+        fields=["UID", "Name", "Data Preparation Cube UID", "State", "Status", "Owner"],
+        local_only=local,
+        mine_only=mine,
+        mlcube=mlcube,
+    )
+
+
+
+ +
+ + +
+ + + +

+prepare(data_uid=typer.Option(..., '--data_uid', '-d', help='Dataset UID'), approval=typer.Option(False, '-y', help='Skip report submission approval step (In this case, it is assumed to be approved)')) + +

+ + +
+ +

Runs the Data preparation step for a raw dataset

+ +
+ Source code in cli/medperf/commands/dataset/dataset.py +
@app.command("prepare")
+@clean_except
+def prepare(
+    data_uid: str = typer.Option(..., "--data_uid", "-d", help="Dataset UID"),
+    approval: bool = typer.Option(
+        False,
+        "-y",
+        help="Skip report submission approval step (In this case, it is assumed to be approved)",
+    ),
+):
+    """Runs the Data preparation step for a raw dataset"""
+    ui = config.ui
+    DataPreparation.run(data_uid, approve_sending_reports=approval)
+    ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+set_operational(data_uid=typer.Option(..., '--data_uid', '-d', help='Dataset UID'), approval=typer.Option(False, '-y', help='Skip confirmation and statistics submission approval step')) + +

+ + +
+ +

Marks a dataset as Operational

+ +
+ Source code in cli/medperf/commands/dataset/dataset.py +
@app.command("set_operational")
+@clean_except
+def set_operational(
+    data_uid: str = typer.Option(..., "--data_uid", "-d", help="Dataset UID"),
+    approval: bool = typer.Option(
+        False, "-y", help="Skip confirmation and statistics submission approval step"
+    ),
+):
+    """Marks a dataset as Operational"""
+    ui = config.ui
+    DatasetSetOperational.run(data_uid, approved=approval)
+    ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+submit(benchmark_uid=typer.Option(None, '--benchmark', '-b', help='UID of the desired benchmark'), data_prep_uid=typer.Option(None, '--data_prep', '-p', help='UID of the desired preparation cube'), data_path=typer.Option(..., '--data_path', '-d', help='Path to the data'), labels_path=typer.Option(..., '--labels_path', '-l', help='Path to the labels'), metadata_path=typer.Option(None, '--metadata_path', '-m', help='Metadata folder location (Might be required if the dataset is already prepared)'), name=typer.Option(..., '--name', help='A human-readable name of the dataset'), description=typer.Option(None, '--description', help='A description of the dataset'), location=typer.Option(None, '--location', help='Location or Institution the data belongs to'), approval=typer.Option(False, '-y', help='Skip approval step'), submit_as_prepared=typer.Option(False, '--submit-as-prepared', help='Use this flag if the dataset is already prepared')) + +

+ + +
+ +

Submits a Dataset instance to the backend

+ +
+ Source code in cli/medperf/commands/dataset/dataset.py +
36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
@app.command("submit")
+@clean_except
+def submit(
+    benchmark_uid: int = typer.Option(
+        None, "--benchmark", "-b", help="UID of the desired benchmark"
+    ),
+    data_prep_uid: int = typer.Option(
+        None, "--data_prep", "-p", help="UID of the desired preparation cube"
+    ),
+    data_path: str = typer.Option(..., "--data_path", "-d", help="Path to the data"),
+    labels_path: str = typer.Option(
+        ..., "--labels_path", "-l", help="Path to the labels"
+    ),
+    metadata_path: str = typer.Option(
+        None,
+        "--metadata_path",
+        "-m",
+        help="Metadata folder location (Might be required if the dataset is already prepared)",
+    ),
+    name: str = typer.Option(
+        ..., "--name", help="A human-readable name of the dataset"
+    ),
+    description: str = typer.Option(
+        None, "--description", help="A description of the dataset"
+    ),
+    location: str = typer.Option(
+        None, "--location", help="Location or Institution the data belongs to"
+    ),
+    approval: bool = typer.Option(False, "-y", help="Skip approval step"),
+    submit_as_prepared: bool = typer.Option(
+        False,
+        "--submit-as-prepared",
+        help="Use this flag if the dataset is already prepared",
+    ),
+):
+    """Submits a Dataset instance to the backend"""
+    ui = config.ui
+    DataCreation.run(
+        benchmark_uid,
+        data_prep_uid,
+        data_path,
+        labels_path,
+        metadata_path,
+        name=name,
+        description=description,
+        location=location,
+        approved=approval,
+        submit_as_prepared=submit_as_prepared,
+    )
+    ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+view(entity_id=typer.Argument(None, help='Dataset ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local datasets if dataset ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user datasets if dataset ID is not provided'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed')) + +

+ + +
+ +

Displays the information of one or more datasets

+ +
+ Source code in cli/medperf/commands/dataset/dataset.py +
@app.command("view")
+@clean_except
+def view(
+    entity_id: Optional[str] = typer.Argument(None, help="Dataset ID"),
+    format: str = typer.Option(
+        "yaml",
+        "-f",
+        "--format",
+        help="Format to display contents. Available formats: [yaml, json]",
+    ),
+    local: bool = typer.Option(
+        False, "--local", help="Display local datasets if dataset ID is not provided"
+    ),
+    mine: bool = typer.Option(
+        False,
+        "--mine",
+        help="Display current-user datasets if dataset ID is not provided",
+    ),
+    output: str = typer.Option(
+        None,
+        "--output",
+        "-o",
+        help="Output file to store contents. If not provided, the output will be displayed",
+    ),
+):
+    """Displays the information of one or more datasets"""
+    EntityView.run(entity_id, Dataset, format, local, mine, output)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/dataset/prepare/index.html b/reference/commands/dataset/prepare/index.html new file mode 100644 index 000000000..d2a1ad70c --- /dev/null +++ b/reference/commands/dataset/prepare/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Prepare - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Prepare

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/dataset/set_operational/index.html b/reference/commands/dataset/set_operational/index.html new file mode 100644 index 000000000..31638e9e5 --- /dev/null +++ b/reference/commands/dataset/set_operational/index.html @@ -0,0 +1,1312 @@ + + + + + + + + + + + + + + + + + + + + Set operational - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Set operational

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ DatasetSetOperational + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/dataset/set_operational.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
class DatasetSetOperational:
+    # TODO: this will be refactored when merging entity edit PR
+    @classmethod
+    def run(cls, dataset_id: int, approved: bool = False):
+        preparation = cls(dataset_id, approved)
+        preparation.validate()
+        preparation.generate_uids()
+        preparation.set_statistics()
+        preparation.set_operational()
+        preparation.update()
+        preparation.write()
+
+        return preparation.dataset.id
+
+    def __init__(self, dataset_id: int, approved: bool):
+        self.ui = config.ui
+        self.dataset = Dataset.get(dataset_id)
+        self.approved = approved
+
+    def validate(self):
+        if self.dataset.state == "OPERATION":
+            raise InvalidArgumentError("The dataset is already operational")
+        if not self.dataset.is_ready():
+            raise InvalidArgumentError("The dataset is not checked")
+
+    def generate_uids(self):
+        """Auto-generates dataset UIDs for both input and output paths"""
+        raw_data_path, raw_labels_path = self.dataset.get_raw_paths()
+        prepared_data_path = self.dataset.data_path
+        prepared_labels_path = self.dataset.labels_path
+
+        in_uid = get_folders_hash([raw_data_path, raw_labels_path])
+        generated_uid = get_folders_hash([prepared_data_path, prepared_labels_path])
+        self.dataset.input_data_hash = in_uid
+        self.dataset.generated_uid = generated_uid
+
+    def set_statistics(self):
+        with open(self.dataset.statistics_path, "r") as f:
+            stats = yaml.safe_load(f)
+        self.dataset.generated_metadata = stats
+
+    def set_operational(self):
+        self.dataset.state = "OPERATION"
+
+    def update(self):
+        body = self.todict()
+        dict_pretty_print(body)
+        msg = "Do you approve sending the presented data to MedPerf? [Y/n] "
+        self.approved = self.approved or approval_prompt(msg)
+
+        if self.approved:
+            config.comms.update_dataset(self.dataset.id, body)
+            return
+
+        raise CleanExit("Setting Dataset as operational was cancelled")
+
+    def todict(self) -> dict:
+        """Dictionary representation of the update body
+
+        Returns:
+            dict: dictionary containing information pertaining the dataset.
+        """
+        return {
+            "input_data_hash": self.dataset.input_data_hash,
+            "generated_uid": self.dataset.generated_uid,
+            "generated_metadata": self.dataset.generated_metadata,
+            "state": self.dataset.state,
+        }
+
+    def write(self) -> str:
+        """Writes the registration into disk
+        Args:
+            filename (str, optional): name of the file. Defaults to config.reg_file.
+        """
+        self.dataset.write()
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+generate_uids() + +

+ + +
+ +

Auto-generates dataset UIDs for both input and output paths

+ +
+ Source code in cli/medperf/commands/dataset/set_operational.py +
33
+34
+35
+36
+37
+38
+39
+40
+41
+42
def generate_uids(self):
+    """Auto-generates dataset UIDs for both input and output paths"""
+    raw_data_path, raw_labels_path = self.dataset.get_raw_paths()
+    prepared_data_path = self.dataset.data_path
+    prepared_labels_path = self.dataset.labels_path
+
+    in_uid = get_folders_hash([raw_data_path, raw_labels_path])
+    generated_uid = get_folders_hash([prepared_data_path, prepared_labels_path])
+    self.dataset.input_data_hash = in_uid
+    self.dataset.generated_uid = generated_uid
+
+
+
+ +
+ + +
+ + + +

+todict() + +

+ + +
+ +

Dictionary representation of the update body

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary containing information pertaining the dataset.

+
+
+ +
+ Source code in cli/medperf/commands/dataset/set_operational.py +
64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
def todict(self) -> dict:
+    """Dictionary representation of the update body
+
+    Returns:
+        dict: dictionary containing information pertaining the dataset.
+    """
+    return {
+        "input_data_hash": self.dataset.input_data_hash,
+        "generated_uid": self.dataset.generated_uid,
+        "generated_metadata": self.dataset.generated_metadata,
+        "state": self.dataset.state,
+    }
+
+
+
+ +
+ + +
+ + + +

+write() + +

+ + +
+ +

Writes the registration into disk

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filename + str + +
+

name of the file. Defaults to config.reg_file.

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/dataset/set_operational.py +
77
+78
+79
+80
+81
+82
def write(self) -> str:
+    """Writes the registration into disk
+    Args:
+        filename (str, optional): name of the file. Defaults to config.reg_file.
+    """
+    self.dataset.write()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/dataset/submit/index.html b/reference/commands/dataset/submit/index.html new file mode 100644 index 000000000..3fe0d729d --- /dev/null +++ b/reference/commands/dataset/submit/index.html @@ -0,0 +1,1441 @@ + + + + + + + + + + + + + + + + + + + + Submit - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Submit

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ DataCreation + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/dataset/submit.py +
class DataCreation:
+    @classmethod
+    def run(
+        cls,
+        benchmark_uid: int,
+        prep_cube_uid: int,
+        data_path: str,
+        labels_path: str,
+        metadata_path: str = None,
+        name: str = None,
+        description: str = None,
+        location: str = None,
+        approved: bool = False,
+        submit_as_prepared: bool = False,
+        for_test: bool = False,
+    ):
+        preparation = cls(
+            benchmark_uid,
+            prep_cube_uid,
+            data_path,
+            labels_path,
+            metadata_path,
+            name,
+            description,
+            location,
+            approved,
+            submit_as_prepared,
+            for_test,
+        )
+        preparation.validate()
+        preparation.validate_prep_cube()
+        preparation.create_dataset_object()
+        if submit_as_prepared:
+            preparation.make_dataset_prepared()
+        updated_dataset_dict = preparation.upload()
+        preparation.to_permanent_path(updated_dataset_dict)
+        preparation.write(updated_dataset_dict)
+
+        return updated_dataset_dict["id"]
+
+    def __init__(
+        self,
+        benchmark_uid: int,
+        prep_cube_uid: int,
+        data_path: str,
+        labels_path: str,
+        metadata_path: str,
+        name: str,
+        description: str,
+        location: str,
+        approved: bool,
+        submit_as_prepared: bool,
+        for_test: bool,
+    ):
+        self.ui = config.ui
+        self.data_path = str(Path(data_path).resolve())
+        self.labels_path = str(Path(labels_path).resolve())
+        self.metadata_path = metadata_path
+        self.name = name
+        self.description = description
+        self.location = location
+        self.benchmark_uid = benchmark_uid
+        self.prep_cube_uid = prep_cube_uid
+        self.approved = approved
+        self.submit_as_prepared = submit_as_prepared
+        self.for_test = for_test
+
+    def validate(self):
+        if not os.path.exists(self.data_path):
+            raise InvalidArgumentError("The provided data path doesn't exist")
+        if not os.path.exists(self.labels_path):
+            raise InvalidArgumentError("The provided labels path doesn't exist")
+
+        if not self.submit_as_prepared and self.metadata_path:
+            raise InvalidArgumentError(
+                "metadata path should only be provided when the dataset is submitted as prepared"
+            )
+        if self.metadata_path:
+            self.metadata_path = str(Path(self.metadata_path).resolve())
+            if not os.path.exists(self.metadata_path):
+                raise InvalidArgumentError("The provided metadata path doesn't exist")
+
+        # TODO: should we check the prep mlcube and accordingly check if metadata path
+        #       is required? For now, we will anyway create an empty metadata folder
+        #       (in self.make_dataset_prepared)
+        too_many_resources = self.benchmark_uid and self.prep_cube_uid
+        no_resource = self.benchmark_uid is None and self.prep_cube_uid is None
+        if no_resource or too_many_resources:
+            raise InvalidArgumentError(
+                "Must provide either a benchmark or a preparation mlcube"
+            )
+
+    def validate_prep_cube(self):
+        if self.prep_cube_uid is None:
+            benchmark = Benchmark.get(self.benchmark_uid)
+            self.prep_cube_uid = benchmark.data_preparation_mlcube
+        Cube.get(self.prep_cube_uid)
+
+    def create_dataset_object(self):
+        """generates dataset UIDs for both input path"""
+        in_uid = get_folders_hash([self.data_path, self.labels_path])
+        dataset = Dataset(
+            name=self.name,
+            description=self.description,
+            location=self.location,
+            data_preparation_mlcube=self.prep_cube_uid,
+            input_data_hash=in_uid,
+            generated_uid=in_uid,
+            split_seed=0,
+            generated_metadata={},
+            state="DEVELOPMENT",
+            submitted_as_prepared=self.submit_as_prepared,
+            for_test=self.for_test,
+        )
+        dataset.write()
+        config.tmp_paths.append(dataset.path)
+        dataset.set_raw_paths(
+            raw_data_path=self.data_path,
+            raw_labels_path=self.labels_path,
+        )
+        self.dataset = dataset
+
+    def make_dataset_prepared(self):
+        shutil.copytree(self.data_path, self.dataset.data_path)
+        shutil.copytree(self.labels_path, self.dataset.labels_path)
+        if self.metadata_path:
+            shutil.copytree(self.metadata_path, self.dataset.metadata_path)
+        else:
+            # Create an empty folder. The statistics logic should
+            # also expect an empty folder to accommodate for users who
+            # have prepared datasets with no the metadata information
+            os.makedirs(self.dataset.metadata_path, exist_ok=True)
+
+    def upload(self):
+        submission_dict = self.dataset.todict()
+        dict_pretty_print(submission_dict)
+        msg = "Do you approve the registration of the presented data to MedPerf? [Y/n] "
+        self.approved = self.approved or approval_prompt(msg)
+
+        if self.approved:
+            updated_body = self.dataset.upload()
+            return updated_body
+
+        raise CleanExit("Dataset submission operation cancelled")
+
+    def to_permanent_path(self, updated_dataset_dict: dict):
+        """Renames the temporary benchmark submission to a permanent one
+
+        Args:
+            bmk_dict (dict): dictionary containing updated information of the submitted benchmark
+        """
+        old_dataset_loc = self.dataset.path
+        updated_dataset = Dataset(**updated_dataset_dict)
+        new_dataset_loc = updated_dataset.path
+        remove_path(new_dataset_loc)
+        os.rename(old_dataset_loc, new_dataset_loc)
+
+    def write(self, updated_dataset_dict):
+        dataset = Dataset(**updated_dataset_dict)
+        dataset.write()
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+create_dataset_object() + +

+ + +
+ +

generates dataset UIDs for both input path

+ +
+ Source code in cli/medperf/commands/dataset/submit.py +
def create_dataset_object(self):
+    """generates dataset UIDs for both input path"""
+    in_uid = get_folders_hash([self.data_path, self.labels_path])
+    dataset = Dataset(
+        name=self.name,
+        description=self.description,
+        location=self.location,
+        data_preparation_mlcube=self.prep_cube_uid,
+        input_data_hash=in_uid,
+        generated_uid=in_uid,
+        split_seed=0,
+        generated_metadata={},
+        state="DEVELOPMENT",
+        submitted_as_prepared=self.submit_as_prepared,
+        for_test=self.for_test,
+    )
+    dataset.write()
+    config.tmp_paths.append(dataset.path)
+    dataset.set_raw_paths(
+        raw_data_path=self.data_path,
+        raw_labels_path=self.labels_path,
+    )
+    self.dataset = dataset
+
+
+
+ +
+ + +
+ + + +

+to_permanent_path(updated_dataset_dict) + +

+ + +
+ +

Renames the temporary benchmark submission to a permanent one

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
bmk_dict + dict + +
+

dictionary containing updated information of the submitted benchmark

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/dataset/submit.py +
def to_permanent_path(self, updated_dataset_dict: dict):
+    """Renames the temporary benchmark submission to a permanent one
+
+    Args:
+        bmk_dict (dict): dictionary containing updated information of the submitted benchmark
+    """
+    old_dataset_loc = self.dataset.path
+    updated_dataset = Dataset(**updated_dataset_dict)
+    new_dataset_loc = updated_dataset.path
+    remove_path(new_dataset_loc)
+    os.rename(old_dataset_loc, new_dataset_loc)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/execution/index.html b/reference/commands/execution/index.html new file mode 100644 index 000000000..b80e1786a --- /dev/null +++ b/reference/commands/execution/index.html @@ -0,0 +1,1324 @@ + + + + + + + + + + + + + + + + + + + + Execution - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Execution

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Execution + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/execution.py +
class Execution:
+    @classmethod
+    def run(
+        cls, dataset: Dataset, model: Cube, evaluator: Cube, ignore_model_errors=False
+    ):
+        """Benchmark execution flow.
+
+        Args:
+            benchmark_uid (int): UID of the desired benchmark
+            data_uid (str): Registered Dataset UID
+            model_uid (int): UID of model to execute
+        """
+        execution = cls(dataset, model, evaluator, ignore_model_errors)
+        execution.prepare()
+        with execution.ui.interactive():
+            execution.run_inference()
+            execution.run_evaluation()
+        execution_summary = execution.todict()
+        return execution_summary
+
+    def __init__(
+        self, dataset: Dataset, model: Cube, evaluator: Cube, ignore_model_errors=False
+    ):
+        self.comms = config.comms
+        self.ui = config.ui
+        self.dataset = dataset
+        self.model = model
+        self.evaluator = evaluator
+        self.ignore_model_errors = ignore_model_errors
+
+    def prepare(self):
+        self.partial = False
+        self.preds_path = self.__setup_predictions_path()
+        self.model_logs_path, self.metrics_logs_path = self.__setup_logs_path()
+        self.results_path = generate_tmp_path()
+        logging.debug(f"tmp results output: {self.results_path}")
+
+    def __setup_logs_path(self):
+        model_uid = self.model.generated_uid
+        eval_uid = self.evaluator.generated_uid
+        data_hash = self.dataset.generated_uid
+
+        logs_path = os.path.join(
+            config.experiments_logs_folder, str(model_uid), str(data_hash)
+        )
+        os.makedirs(logs_path, exist_ok=True)
+        model_logs_path = os.path.join(logs_path, "model.log")
+        metrics_logs_path = os.path.join(logs_path, f"metrics_{eval_uid}.log")
+        return model_logs_path, metrics_logs_path
+
+    def __setup_predictions_path(self):
+        model_uid = self.model.generated_uid
+        data_hash = self.dataset.generated_uid
+        preds_path = os.path.join(
+            config.predictions_folder, str(model_uid), str(data_hash)
+        )
+        if os.path.exists(preds_path):
+            msg = f"Found existing predictions for model {self.model.id} on dataset "
+            msg += f"{self.dataset.id} at {preds_path}. Consider deleting this "
+            msg += "folder if you wish to overwrite the predictions."
+            raise ExecutionError(msg)
+        return preds_path
+
+    def run_inference(self):
+        self.ui.text = "Running model inference on dataset"
+        infer_timeout = config.infer_timeout
+        preds_path = self.preds_path
+        data_path = self.dataset.data_path
+        try:
+            self.model.run(
+                task="infer",
+                output_logs=self.model_logs_path,
+                timeout=infer_timeout,
+                data_path=data_path,
+                output_path=preds_path,
+            )
+            self.ui.print("> Model execution complete")
+
+        except ExecutionError as e:
+            if not self.ignore_model_errors:
+                logging.error(f"Model MLCube Execution failed: {e}")
+                raise ExecutionError(f"Model MLCube failed: {e}")
+            else:
+                self.partial = True
+                logging.warning(f"Model MLCube Execution failed: {e}")
+
+    def run_evaluation(self):
+        self.ui.text = "Running model evaluation on dataset"
+        evaluate_timeout = config.evaluate_timeout
+        preds_path = self.preds_path
+        labels_path = self.dataset.labels_path
+        results_path = self.results_path
+        self.ui.text = "Evaluating results"
+        try:
+            self.evaluator.run(
+                task="evaluate",
+                output_logs=self.metrics_logs_path,
+                timeout=evaluate_timeout,
+                predictions=preds_path,
+                labels=labels_path,
+                output_path=results_path,
+            )
+        except ExecutionError as e:
+            logging.error(f"Metrics MLCube Execution failed: {e}")
+            raise ExecutionError("Metrics MLCube failed")
+
+    def todict(self):
+        return {
+            "results": self.get_results(),
+            "partial": self.partial,
+        }
+
+    def get_results(self):
+        with open(self.results_path, "r") as f:
+            results = yaml.safe_load(f)
+        return results
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(dataset, model, evaluator, ignore_model_errors=False) + + + classmethod + + +

+ + +
+ +

Benchmark execution flow.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

UID of the desired benchmark

+
+
+ required +
data_uid + str + +
+

Registered Dataset UID

+
+
+ required +
model_uid + int + +
+

UID of model to execute

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/execution.py +
13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
@classmethod
+def run(
+    cls, dataset: Dataset, model: Cube, evaluator: Cube, ignore_model_errors=False
+):
+    """Benchmark execution flow.
+
+    Args:
+        benchmark_uid (int): UID of the desired benchmark
+        data_uid (str): Registered Dataset UID
+        model_uid (int): UID of model to execute
+    """
+    execution = cls(dataset, model, evaluator, ignore_model_errors)
+    execution.prepare()
+    with execution.ui.interactive():
+        execution.run_inference()
+        execution.run_evaluation()
+    execution_summary = execution.todict()
+    return execution_summary
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/list/index.html b/reference/commands/list/index.html new file mode 100644 index 000000000..bd70d28b8 --- /dev/null +++ b/reference/commands/list/index.html @@ -0,0 +1,1214 @@ + + + + + + + + + + + + + + + + + + + + List - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

List

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ EntityList + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/list.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
class EntityList:
+    @staticmethod
+    def run(
+        entity_class,
+        fields,
+        local_only: bool = False,
+        mine_only: bool = False,
+        **kwargs,
+    ):
+        """Lists all local datasets
+
+        Args:
+            local_only (bool, optional): Display all local results. Defaults to False.
+            mine_only (bool, optional): Display all current-user results. Defaults to False.
+            kwargs (dict): Additional parameters for filtering entity lists.
+        """
+        entity_list = EntityList(entity_class, fields, local_only, mine_only, **kwargs)
+        entity_list.prepare()
+        entity_list.validate()
+        entity_list.filter()
+        entity_list.display()
+
+    def __init__(self, entity_class, fields, local_only, mine_only, **kwargs):
+        self.entity_class = entity_class
+        self.fields = fields
+        self.local_only = local_only
+        self.mine_only = mine_only
+        self.filters = kwargs
+        self.data = []
+
+    def prepare(self):
+        if self.mine_only:
+            self.filters["owner"] = get_medperf_user_data()["id"]
+
+        entities = self.entity_class.all(
+            local_only=self.local_only, filters=self.filters
+        )
+        self.data = [entity.display_dict() for entity in entities]
+
+    def validate(self):
+        if self.data:
+            valid_fields = set(self.data[0].keys())
+            chosen_fields = set(self.fields)
+            if not chosen_fields.issubset(valid_fields):
+                invalid_fields = chosen_fields.difference(valid_fields)
+                invalid_fields = ", ".join(invalid_fields)
+                raise InvalidArgumentError(f"Invalid field(s): {invalid_fields}")
+
+    def filter(self):
+        self.data = [
+            {field: entity_dict[field] for field in self.fields}
+            for entity_dict in self.data
+        ]
+
+    def display(self):
+        headers = self.fields
+        data_lists = [list(entity_dict.values()) for entity_dict in self.data]
+        tab = tabulate(data_lists, headers=headers)
+        config.ui.print(tab)
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(entity_class, fields, local_only=False, mine_only=False, **kwargs) + + + staticmethod + + +

+ + +
+ +

Lists all local datasets

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
local_only + bool + +
+

Display all local results. Defaults to False.

+
+
+ False +
mine_only + bool + +
+

Display all current-user results. Defaults to False.

+
+
+ False +
kwargs + dict + +
+

Additional parameters for filtering entity lists.

+
+
+ {} +
+ +
+ Source code in cli/medperf/commands/list.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
@staticmethod
+def run(
+    entity_class,
+    fields,
+    local_only: bool = False,
+    mine_only: bool = False,
+    **kwargs,
+):
+    """Lists all local datasets
+
+    Args:
+        local_only (bool, optional): Display all local results. Defaults to False.
+        mine_only (bool, optional): Display all current-user results. Defaults to False.
+        kwargs (dict): Additional parameters for filtering entity lists.
+    """
+    entity_list = EntityList(entity_class, fields, local_only, mine_only, **kwargs)
+    entity_list.prepare()
+    entity_list.validate()
+    entity_list.filter()
+    entity_list.display()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/mlcube/associate/index.html b/reference/commands/mlcube/associate/index.html new file mode 100644 index 000000000..c13032f86 --- /dev/null +++ b/reference/commands/mlcube/associate/index.html @@ -0,0 +1,1206 @@ + + + + + + + + + + + + + + + + + + + + Associate - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Associate

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ AssociateCube + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/mlcube/associate.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
class AssociateCube:
+    @classmethod
+    def run(
+        cls,
+        cube_uid: int,
+        benchmark_uid: int,
+        approved=False,
+        no_cache=False,
+    ):
+        """Associates a cube with a given benchmark
+
+        Args:
+            cube_uid (int): UID of model MLCube
+            benchmark_uid (int): UID of benchmark
+            approved (bool): Skip validation step. Defualts to False
+        """
+        comms = config.comms
+        ui = config.ui
+        cube = Cube.get(cube_uid)
+        benchmark = Benchmark.get(benchmark_uid)
+
+        _, results = CompatibilityTestExecution.run(
+            benchmark=benchmark_uid, model=cube_uid, no_cache=no_cache
+        )
+        ui.print("These are the results generated by the compatibility test. ")
+        ui.print("This will be sent along the association request.")
+        ui.print("They will not be part of the benchmark.")
+        dict_pretty_print(results)
+
+        msg = "Please confirm that you would like to associate "
+        msg += f"the MLCube '{cube.name}' with the benchmark '{benchmark.name}' [Y/n]"
+        approved = approved or approval_prompt(msg)
+        if approved:
+            ui.print("Generating mlcube benchmark association")
+            metadata = {"test_result": results}
+            comms.associate_cube(cube_uid, benchmark_uid, metadata)
+        else:
+            ui.print("MLCube association operation cancelled")
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(cube_uid, benchmark_uid, approved=False, no_cache=False) + + + classmethod + + +

+ + +
+ +

Associates a cube with a given benchmark

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
cube_uid + int + +
+

UID of model MLCube

+
+
+ required +
benchmark_uid + int + +
+

UID of benchmark

+
+
+ required +
approved + bool + +
+

Skip validation step. Defualts to False

+
+
+ False +
+ +
+ Source code in cli/medperf/commands/mlcube/associate.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
@classmethod
+def run(
+    cls,
+    cube_uid: int,
+    benchmark_uid: int,
+    approved=False,
+    no_cache=False,
+):
+    """Associates a cube with a given benchmark
+
+    Args:
+        cube_uid (int): UID of model MLCube
+        benchmark_uid (int): UID of benchmark
+        approved (bool): Skip validation step. Defualts to False
+    """
+    comms = config.comms
+    ui = config.ui
+    cube = Cube.get(cube_uid)
+    benchmark = Benchmark.get(benchmark_uid)
+
+    _, results = CompatibilityTestExecution.run(
+        benchmark=benchmark_uid, model=cube_uid, no_cache=no_cache
+    )
+    ui.print("These are the results generated by the compatibility test. ")
+    ui.print("This will be sent along the association request.")
+    ui.print("They will not be part of the benchmark.")
+    dict_pretty_print(results)
+
+    msg = "Please confirm that you would like to associate "
+    msg += f"the MLCube '{cube.name}' with the benchmark '{benchmark.name}' [Y/n]"
+    approved = approved or approval_prompt(msg)
+    if approved:
+        ui.print("Generating mlcube benchmark association")
+        metadata = {"test_result": results}
+        comms.associate_cube(cube_uid, benchmark_uid, metadata)
+    else:
+        ui.print("MLCube association operation cancelled")
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/mlcube/create/index.html b/reference/commands/mlcube/create/index.html new file mode 100644 index 000000000..56ed99880 --- /dev/null +++ b/reference/commands/mlcube/create/index.html @@ -0,0 +1,1182 @@ + + + + + + + + + + + + + + + + + + + + Create - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Create

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ CreateCube + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/mlcube/create.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
class CreateCube:
+    @classmethod
+    def run(cls, template_name: str, output_path: str = ".", config_file: str = None):
+        """Creates a new MLCube based on one of the provided templates
+
+        Args:
+            template_name (str): The name of the template to use
+            output_path (str, Optional): The desired path for the MLCube. Defaults to current path.
+            config_file (str, Optional): Path to a JSON configuration file. If not passed, user is prompted.
+        """
+        template_dirs = config.templates
+        if template_name not in template_dirs:
+            templates = list(template_dirs.keys())
+            raise InvalidArgumentError(
+                f"Invalid template name. Available templates: [{' | '.join(templates)}]"
+            )
+
+        no_input = False
+        if config_file is not None:
+            no_input = True
+
+        # Get package parent path
+        path = abspath(Path(__file__).parent.parent.parent)
+
+        template_dir = template_dirs[template_name]
+        cookiecutter(
+            path,
+            directory=template_dir,
+            output_dir=output_path,
+            config_file=config_file,
+            no_input=no_input,
+        )
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(template_name, output_path='.', config_file=None) + + + classmethod + + +

+ + +
+ +

Creates a new MLCube based on one of the provided templates

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
template_name + str + +
+

The name of the template to use

+
+
+ required +
output_path + (str, Optional) + +
+

The desired path for the MLCube. Defaults to current path.

+
+
+ '.' +
config_file + (str, Optional) + +
+

Path to a JSON configuration file. If not passed, user is prompted.

+
+
+ None +
+ +
+ Source code in cli/medperf/commands/mlcube/create.py +
10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
@classmethod
+def run(cls, template_name: str, output_path: str = ".", config_file: str = None):
+    """Creates a new MLCube based on one of the provided templates
+
+    Args:
+        template_name (str): The name of the template to use
+        output_path (str, Optional): The desired path for the MLCube. Defaults to current path.
+        config_file (str, Optional): Path to a JSON configuration file. If not passed, user is prompted.
+    """
+    template_dirs = config.templates
+    if template_name not in template_dirs:
+        templates = list(template_dirs.keys())
+        raise InvalidArgumentError(
+            f"Invalid template name. Available templates: [{' | '.join(templates)}]"
+        )
+
+    no_input = False
+    if config_file is not None:
+        no_input = True
+
+    # Get package parent path
+    path = abspath(Path(__file__).parent.parent.parent)
+
+    template_dir = template_dirs[template_name]
+    cookiecutter(
+        path,
+        directory=template_dir,
+        output_dir=output_path,
+        config_file=config_file,
+        no_input=no_input,
+    )
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/mlcube/mlcube/index.html b/reference/commands/mlcube/mlcube/index.html new file mode 100644 index 000000000..f91481a8c --- /dev/null +++ b/reference/commands/mlcube/mlcube/index.html @@ -0,0 +1,1376 @@ + + + + + + + + + + + + + + + + + + + + Mlcube - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Mlcube

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+associate(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), model_uid=typer.Option(..., '--model_uid', '-m', help='Model UID'), approval=typer.Option(False, '-y', help='Skip approval step'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist')) + +

+ + +
+ +

Associates an MLCube to a benchmark

+ +
+ Source code in cli/medperf/commands/mlcube/mlcube.py +
@app.command("associate")
+@clean_except
+def associate(
+    benchmark_uid: int = typer.Option(..., "--benchmark", "-b", help="Benchmark UID"),
+    model_uid: int = typer.Option(..., "--model_uid", "-m", help="Model UID"),
+    approval: bool = typer.Option(False, "-y", help="Skip approval step"),
+    no_cache: bool = typer.Option(
+        False,
+        "--no-cache",
+        help="Execute the test even if results already exist",
+    ),
+):
+    """Associates an MLCube to a benchmark"""
+    AssociateCube.run(model_uid, benchmark_uid, approved=approval, no_cache=no_cache)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+create(template=typer.Argument(..., help=f'MLCube template name. Available templates: [{' | '.join(config.templates.keys())}]'), output_path=typer.Option('.', '--output', '-o', help='Save the generated MLCube to the specified path'), config_file=typer.Option(None, '--config-file', '-c', help='JSON Configuration file. If not present then user is prompted for configuration')) + +

+ + +
+ +

Creates an MLCube based on one of the specified templates

+ +
+ Source code in cli/medperf/commands/mlcube/mlcube.py +
31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
@app.command("create")
+@clean_except
+def create(
+    template: str = typer.Argument(
+        ...,
+        help=f"MLCube template name. Available templates: [{' | '.join(config.templates.keys())}]",
+    ),
+    output_path: str = typer.Option(
+        ".", "--output", "-o", help="Save the generated MLCube to the specified path"
+    ),
+    config_file: str = typer.Option(
+        None,
+        "--config-file",
+        "-c",
+        help="JSON Configuration file. If not present then user is prompted for configuration",
+    ),
+):
+    """Creates an MLCube based on one of the specified templates"""
+    CreateCube.run(template, output_path, config_file)
+
+
+
+ +
+ + +
+ + + +

+list(local=typer.Option(False, '--local', help='Get local mlcubes'), mine=typer.Option(False, '--mine', help='Get current-user mlcubes')) + +

+ + +
+ +

List mlcubes stored locally and remotely from the user

+ +
+ Source code in cli/medperf/commands/mlcube/mlcube.py +
16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
@app.command("ls")
+@clean_except
+def list(
+    local: bool = typer.Option(False, "--local", help="Get local mlcubes"),
+    mine: bool = typer.Option(False, "--mine", help="Get current-user mlcubes"),
+):
+    """List mlcubes stored locally and remotely from the user"""
+    EntityList.run(
+        Cube,
+        fields=["UID", "Name", "State", "Registered"],
+        local_only=local,
+        mine_only=mine,
+    )
+
+
+
+ +
+ + +
+ + + +

+submit(name=typer.Option(..., '--name', '-n', help='Name of the mlcube'), mlcube_file=typer.Option(..., '--mlcube-file', '-m', help='Identifier to download the mlcube file. See the description above'), mlcube_hash=typer.Option('', '--mlcube-hash', help='hash of mlcube file'), parameters_file=typer.Option('', '--parameters-file', '-p', help='Identifier to download the parameters file. See the description above'), parameters_hash=typer.Option('', '--parameters-hash', help='hash of parameters file'), additional_file=typer.Option('', '--additional-file', '-a', help='Identifier to download the additional files tarball. See the description above'), additional_hash=typer.Option('', '--additional-hash', help='hash of additional file'), image_file=typer.Option('', '--image-file', '-i', help='Identifier to download the image file. See the description above'), image_hash=typer.Option('', '--image-hash', help='hash of image file'), operational=typer.Option(False, '--operational', help='Submit the MLCube as OPERATIONAL')) + +

+ + +
+ +

Submits a new cube to the platform.

+ +
+ The following assets +
    +
  • +

    mlcube_file

    +
  • +
  • +

    parameters_file

    +
  • +
  • +

    additional_file

    +
  • +
  • +

    image_file

    +
  • +
+

are expected to be given in the following format: +where source_prefix instructs the client how to download the resource, and resource_identifier +is the identifier used to download the asset. The following are supported:

+
    +
  1. +

    A direct link: "direct:"

    +
  2. +
  3. +

    An asset hosted on the Synapse platform: "synapse:"

    +
  4. +
+

If a URL is given without a source prefix, it will be treated as a direct download link.

+ +
+ Source code in cli/medperf/commands/mlcube/mlcube.py +
@app.command("submit")
+@clean_except
+def submit(
+    name: str = typer.Option(..., "--name", "-n", help="Name of the mlcube"),
+    mlcube_file: str = typer.Option(
+        ...,
+        "--mlcube-file",
+        "-m",
+        help="Identifier to download the mlcube file. See the description above",
+    ),
+    mlcube_hash: str = typer.Option("", "--mlcube-hash", help="hash of mlcube file"),
+    parameters_file: str = typer.Option(
+        "",
+        "--parameters-file",
+        "-p",
+        help="Identifier to download the parameters file. See the description above",
+    ),
+    parameters_hash: str = typer.Option(
+        "", "--parameters-hash", help="hash of parameters file"
+    ),
+    additional_file: str = typer.Option(
+        "",
+        "--additional-file",
+        "-a",
+        help="Identifier to download the additional files tarball. See the description above",
+    ),
+    additional_hash: str = typer.Option(
+        "", "--additional-hash", help="hash of additional file"
+    ),
+    image_file: str = typer.Option(
+        "",
+        "--image-file",
+        "-i",
+        help="Identifier to download the image file. See the description above",
+    ),
+    image_hash: str = typer.Option("", "--image-hash", help="hash of image file"),
+    operational: bool = typer.Option(
+        False,
+        "--operational",
+        help="Submit the MLCube as OPERATIONAL",
+    ),
+):
+    """Submits a new cube to the platform.\n
+    The following assets:\n
+        - mlcube_file\n
+        - parameters_file\n
+        - additional_file\n
+        - image_file\n
+    are expected to be given in the following format: <source_prefix:resource_identifier>
+    where `source_prefix` instructs the client how to download the resource, and `resource_identifier`
+    is the identifier used to download the asset. The following are supported:\n
+    1. A direct link: "direct:<URL>"\n
+    2. An asset hosted on the Synapse platform: "synapse:<synapse ID>"\n\n
+
+    If a URL is given without a source prefix, it will be treated as a direct download link.
+    """
+    mlcube_info = {
+        "name": name,
+        "git_mlcube_url": mlcube_file,
+        "git_mlcube_hash": mlcube_hash,
+        "git_parameters_url": parameters_file,
+        "parameters_hash": parameters_hash,
+        "image_tarball_url": image_file,
+        "image_tarball_hash": image_hash,
+        "additional_files_tarball_url": additional_file,
+        "additional_files_tarball_hash": additional_hash,
+        "state": "OPERATION" if operational else "DEVELOPMENT",
+    }
+    SubmitCube.run(mlcube_info)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+view(entity_id=typer.Argument(None, help='MLCube ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local mlcubes if mlcube ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user mlcubes if mlcube ID is not provided'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed')) + +

+ + +
+ +

Displays the information of one or more mlcubes

+ +
+ Source code in cli/medperf/commands/mlcube/mlcube.py +
@app.command("view")
+@clean_except
+def view(
+    entity_id: Optional[int] = typer.Argument(None, help="MLCube ID"),
+    format: str = typer.Option(
+        "yaml",
+        "-f",
+        "--format",
+        help="Format to display contents. Available formats: [yaml, json]",
+    ),
+    local: bool = typer.Option(
+        False, "--local", help="Display local mlcubes if mlcube ID is not provided"
+    ),
+    mine: bool = typer.Option(
+        False,
+        "--mine",
+        help="Display current-user mlcubes if mlcube ID is not provided",
+    ),
+    output: str = typer.Option(
+        None,
+        "--output",
+        "-o",
+        help="Output file to store contents. If not provided, the output will be displayed",
+    ),
+):
+    """Displays the information of one or more mlcubes"""
+    EntityView.run(entity_id, Cube, format, local, mine, output)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/mlcube/submit/index.html b/reference/commands/mlcube/submit/index.html new file mode 100644 index 000000000..5fd3b0275 --- /dev/null +++ b/reference/commands/mlcube/submit/index.html @@ -0,0 +1,1202 @@ + + + + + + + + + + + + + + + + + + + + Submit - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Submit

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ SubmitCube + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/mlcube/submit.py +
 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
class SubmitCube:
+    @classmethod
+    def run(cls, submit_info: dict):
+        """Submits a new cube to the medperf platform
+
+        Args:
+            submit_info (dict): Dictionary containing the cube information.
+        """
+        ui = config.ui
+        submission = cls(submit_info)
+
+        with ui.interactive():
+            ui.text = "Validating MLCube can be downloaded"
+            submission.download()
+            ui.text = "Submitting MLCube to MedPerf"
+            updated_cube_dict = submission.upload()
+            submission.to_permanent_path(updated_cube_dict)
+            submission.write(updated_cube_dict)
+
+    def __init__(self, submit_info: dict):
+        self.comms = config.comms
+        self.ui = config.ui
+        self.cube = Cube(**submit_info)
+        config.tmp_paths.append(self.cube.path)
+
+    def download(self):
+        self.cube.download_config_files()
+        self.cube.download_run_files()
+
+    def upload(self):
+        updated_body = self.cube.upload()
+        return updated_body
+
+    def to_permanent_path(self, cube_dict):
+        """Renames the temporary cube submission to a permanent one using the uid of
+        the registered cube
+        """
+        old_cube_loc = self.cube.path
+        updated_cube = Cube(**cube_dict)
+        new_cube_loc = updated_cube.path
+        remove_path(new_cube_loc)
+        os.rename(old_cube_loc, new_cube_loc)
+
+    def write(self, updated_cube_dict):
+        cube = Cube(**updated_cube_dict)
+        cube.write()
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(submit_info) + + + classmethod + + +

+ + +
+ +

Submits a new cube to the medperf platform

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
submit_info + dict + +
+

Dictionary containing the cube information.

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/mlcube/submit.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
@classmethod
+def run(cls, submit_info: dict):
+    """Submits a new cube to the medperf platform
+
+    Args:
+        submit_info (dict): Dictionary containing the cube information.
+    """
+    ui = config.ui
+    submission = cls(submit_info)
+
+    with ui.interactive():
+        ui.text = "Validating MLCube can be downloaded"
+        submission.download()
+        ui.text = "Submitting MLCube to MedPerf"
+        updated_cube_dict = submission.upload()
+        submission.to_permanent_path(updated_cube_dict)
+        submission.write(updated_cube_dict)
+
+
+
+ +
+ + +
+ + + +

+to_permanent_path(cube_dict) + +

+ + +
+ +

Renames the temporary cube submission to a permanent one using the uid of +the registered cube

+ +
+ Source code in cli/medperf/commands/mlcube/submit.py +
41
+42
+43
+44
+45
+46
+47
+48
+49
def to_permanent_path(self, cube_dict):
+    """Renames the temporary cube submission to a permanent one using the uid of
+    the registered cube
+    """
+    old_cube_loc = self.cube.path
+    updated_cube = Cube(**cube_dict)
+    new_cube_loc = updated_cube.path
+    remove_path(new_cube_loc)
+    os.rename(old_cube_loc, new_cube_loc)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/profile/index.html b/reference/commands/profile/index.html new file mode 100644 index 000000000..eda5f02a8 --- /dev/null +++ b/reference/commands/profile/index.html @@ -0,0 +1,1366 @@ + + + + + + + + + + + + + + + + + + + + Profile - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Profile

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+activate(profile) + +

+ + +
+ +

Assigns the active profile, which is used by default

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
profile + str + +
+

Name of the profile to be used.

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/profile.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
@app.command("activate")
+@clean_except
+def activate(profile: str):
+    """Assigns the active profile, which is used by default
+
+    Args:
+        profile (str): Name of the profile to be used.
+    """
+    config_p = read_config()
+
+    if profile not in config_p:
+        raise InvalidArgumentError("The provided profile does not exists")
+
+    config_p.activate(profile)
+    write_config(config_p)
+
+
+
+ +
+ + +
+ + + +

+create(ctx, name=typer.Option(..., '--name', '-n', help="Profile's name")) + +

+ + +
+ +

Creates a new profile for managing and customizing configuration

+ +
+ Source code in cli/medperf/commands/profile.py +
29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
@app.command("create")
+@clean_except
+@configurable
+def create(
+    ctx: typer.Context,
+    name: str = typer.Option(..., "--name", "-n", help="Profile's name"),
+):
+    """Creates a new profile for managing and customizing configuration"""
+    args = ctx.params
+    args.pop("name")
+    config_p = read_config()
+
+    if name in config_p:
+        raise InvalidArgumentError("A profile with the same name already exists")
+
+    config_p[name] = args
+    write_config(config_p)
+
+
+
+ +
+ + +
+ + + +

+delete(profile) + +

+ + +
+ +

Deletes a profile's configuration.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
profile + str + +
+

Profile to delete.

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/profile.py +
@app.command("delete")
+@clean_except
+def delete(profile: str):
+    """Deletes a profile's configuration.
+
+    Args:
+        profile (str): Profile to delete.
+    """
+    config_p = read_config()
+    if profile not in config_p.profiles:
+        raise InvalidArgumentError("The provided profile does not exists")
+
+    if profile in [
+        config.default_profile_name,
+        config.testauth_profile_name,
+        config.test_profile_name,
+    ]:
+        raise InvalidArgumentError("Cannot delete reserved profiles")
+
+    if config_p.is_profile_active(profile):
+        raise InvalidArgumentError("Cannot delete a currently activated profile")
+
+    del config_p[profile]
+    write_config(config_p)
+
+
+
+ +
+ + +
+ + + +

+list() + +

+ + +
+ +

Lists all available profiles

+ +
+ Source code in cli/medperf/commands/profile.py +
60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
@app.command("ls")
+@clean_except
+def list():
+    """Lists all available profiles"""
+    ui = config.ui
+    config_p = read_config()
+    for profile in config_p:
+        if config_p.is_profile_active(profile):
+            ui.print_highlight("* " + profile)
+        else:
+            ui.print("  " + profile)
+
+
+
+ +
+ + +
+ + + +

+set_args(ctx) + +

+ + +
+ +

Assign key-value configuration pairs to the current profile.

+ +
+ Source code in cli/medperf/commands/profile.py +
48
+49
+50
+51
+52
+53
+54
+55
+56
+57
@app.command("set")
+@clean_except
+@configurable
+def set_args(ctx: typer.Context):
+    """Assign key-value configuration pairs to the current profile."""
+    args = ctx.params
+    config_p = read_config()
+
+    config_p.active_profile.update(args)
+    write_config(config_p)
+
+
+
+ +
+ + +
+ + + +

+view(profile=typer.Argument(None)) + +

+ + +
+ +

Displays a profile's configuration.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
profile + str + +
+

Profile to display information from. Defaults to active profile.

+
+
+ typer.Argument(None) +
+ +
+ Source code in cli/medperf/commands/profile.py +
73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
@app.command("view")
+@clean_except
+def view(profile: str = typer.Argument(None)):
+    """Displays a profile's configuration.
+
+    Args:
+        profile (str, optional): Profile to display information from. Defaults to active profile.
+    """
+    config_p = read_config()
+    profile_config = config_p.active_profile
+    if profile:
+        profile_config = config_p[profile]
+
+    profile_config.pop(config.credentials_keyword, None)
+    profile_name = profile if profile else config_p.active_profile_name
+    config.ui.print(f"\nProfile '{profile_name}':")
+    dict_pretty_print(profile_config, skip_none_values=False)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/result/create/index.html b/reference/commands/result/create/index.html new file mode 100644 index 000000000..ab375f550 --- /dev/null +++ b/reference/commands/result/create/index.html @@ -0,0 +1,1695 @@ + + + + + + + + + + + + + + + + + + + + Create - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Create

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ BenchmarkExecution + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/result/create.py +
class BenchmarkExecution:
+    @classmethod
+    def run(
+        cls,
+        benchmark_uid: int,
+        data_uid: int,
+        models_uids: Optional[List[int]] = None,
+        models_input_file: Optional[str] = None,
+        ignore_model_errors=False,
+        ignore_failed_experiments=False,
+        no_cache=False,
+        show_summary=False,
+    ):
+        """Benchmark execution flow.
+
+        Args:
+            benchmark_uid (int): UID of the desired benchmark
+            data_uid (str): Registered Dataset UID
+            models_uids (List|None): list of model UIDs to execute.
+                                    if None, models_input_file will be used
+            models_input_file: filename to read from
+            if models_uids and models_input_file are None, use all benchmark models
+        """
+        execution = cls(
+            benchmark_uid,
+            data_uid,
+            models_uids,
+            models_input_file,
+            ignore_model_errors,
+            ignore_failed_experiments,
+        )
+        execution.prepare()
+        execution.validate()
+        execution.prepare_models()
+        if not no_cache:
+            execution.load_cached_results()
+        with execution.ui.interactive():
+            results = execution.run_experiments()
+        if show_summary:
+            execution.print_summary()
+        return results
+
+    def __init__(
+        self,
+        benchmark_uid: int,
+        data_uid: int,
+        models_uids,
+        models_input_file: str = None,
+        ignore_model_errors=False,
+        ignore_failed_experiments=False,
+    ):
+        self.benchmark_uid = benchmark_uid
+        self.data_uid = data_uid
+        self.models_uids = models_uids
+        self.models_input_file = models_input_file
+        self.ui = config.ui
+        self.evaluator = None
+        self.ignore_model_errors = ignore_model_errors
+        self.ignore_failed_experiments = ignore_failed_experiments
+        self.cached_results = {}
+        self.experiments = []
+
+    def prepare(self):
+        self.benchmark = Benchmark.get(self.benchmark_uid)
+        self.ui.print(f"Benchmark Execution: {self.benchmark.name}")
+        self.dataset = Dataset.get(self.data_uid)
+        evaluator_uid = self.benchmark.data_evaluator_mlcube
+        self.evaluator = self.__get_cube(evaluator_uid, "Evaluator")
+
+    def validate(self):
+        dset_prep_cube = self.dataset.data_preparation_mlcube
+        bmark_prep_cube = self.benchmark.data_preparation_mlcube
+
+        if self.dataset.id is None:
+            msg = "The provided dataset is not registered."
+            raise InvalidArgumentError(msg)
+
+        if self.dataset.state != "OPERATION":
+            msg = "The provided dataset is not operational."
+            raise InvalidArgumentError(msg)
+
+        if dset_prep_cube != bmark_prep_cube:
+            msg = "The provided dataset is not compatible with the specified benchmark."
+            raise InvalidArgumentError(msg)
+
+    def prepare_models(self):
+        if self.models_input_file:
+            self.models_uids = self.__get_models_from_file()
+
+        if self.models_uids == [self.benchmark.reference_model_mlcube]:
+            # avoid the need of sending a request to the server for
+            # finding the benchmark's associated models
+            return
+
+        benchmark_models = Benchmark.get_models_uids(self.benchmark_uid)
+        benchmark_models.append(self.benchmark.reference_model_mlcube)
+
+        if self.models_uids is None:
+            self.models_uids = benchmark_models
+        else:
+            self.__validate_models(benchmark_models)
+
+    def __get_models_from_file(self):
+        if not os.path.exists(self.models_input_file):
+            raise InvalidArgumentError("The given file does not exist")
+        with open(self.models_input_file) as f:
+            text = f.read()
+        models = text.strip().split(",")
+        try:
+            return list(map(int, models))
+        except ValueError as e:
+            msg = f"Could not parse the given file: {e}. "
+            msg += "The file should contain a list of comma-separated integers"
+            raise InvalidArgumentError(msg)
+
+    def __validate_models(self, benchmark_models):
+        models_set = set(self.models_uids)
+        benchmark_models_set = set(benchmark_models)
+        non_assoc_cubes = models_set.difference(benchmark_models_set)
+        if non_assoc_cubes:
+            if len(non_assoc_cubes) > 1:
+                msg = f"Model of UID {non_assoc_cubes} is not associated with the specified benchmark."
+            else:
+                msg = f"Models of UIDs {non_assoc_cubes} are not associated with the specified benchmark."
+            raise InvalidArgumentError(msg)
+
+    def load_cached_results(self):
+        results = Result.all()
+        benchmark_dset_results = [
+            result
+            for result in results
+            if result.benchmark == self.benchmark_uid
+            and result.dataset == self.data_uid
+        ]
+        self.cached_results = {
+            result.model: result for result in benchmark_dset_results
+        }
+
+    def __get_cube(self, uid: int, name: str) -> Cube:
+        self.ui.text = f"Retrieving {name} cube"
+        cube = Cube.get(uid)
+        cube.download_run_files()
+        self.ui.print(f"> {name} cube download complete")
+        return cube
+
+    def run_experiments(self):
+        for model_uid in self.models_uids:
+            if model_uid in self.cached_results:
+                self.experiments.append(
+                    {
+                        "model_uid": model_uid,
+                        "result": self.cached_results[model_uid],
+                        "cached": True,
+                        "error": "",
+                    }
+                )
+                continue
+
+            try:
+                model_cube = self.__get_cube(model_uid, "Model")
+                execution_summary = Execution.run(
+                    dataset=self.dataset,
+                    model=model_cube,
+                    evaluator=self.evaluator,
+                    ignore_model_errors=self.ignore_model_errors,
+                )
+            except MedperfException as e:
+                self.__handle_experiment_error(model_uid, e)
+                self.experiments.append(
+                    {
+                        "model_uid": model_uid,
+                        "result": None,
+                        "cached": False,
+                        "error": str(e),
+                    }
+                )
+                continue
+
+            partial = execution_summary["partial"]
+            results = execution_summary["results"]
+            result = self.__write_result(model_uid, results, partial)
+
+            self.experiments.append(
+                {
+                    "model_uid": model_uid,
+                    "result": result,
+                    "cached": False,
+                    "error": "",
+                }
+            )
+        return [experiment["result"] for experiment in self.experiments]
+
+    def __handle_experiment_error(self, model_uid, exception):
+        if isinstance(exception, InvalidEntityError):
+            config.ui.print_error(
+                f"There was an error when retrieving the model mlcube {model_uid}: {exception}"
+            )
+        elif isinstance(exception, ExecutionError):
+            config.ui.print_error(
+                f"There was an error when executing the benchmark with the model {model_uid}: {exception}"
+            )
+        else:
+            raise exception
+        if not self.ignore_failed_experiments:
+            raise exception
+
+    def __result_dict(self, model_uid, results, partial):
+        return {
+            "name": f"b{self.benchmark_uid}m{model_uid}d{self.data_uid}",
+            "benchmark": self.benchmark_uid,
+            "model": model_uid,
+            "dataset": self.data_uid,
+            "results": results,
+            "metadata": {"partial": partial},
+        }
+
+    def __write_result(self, model_uid, results, partial):
+        results_info = self.__result_dict(model_uid, results, partial)
+        result = Result(**results_info)
+        result.write()
+        return result
+
+    def print_summary(self):
+        headers = ["model", "local result UID", "partial result", "from cache", "error"]
+        data_lists_for_display = []
+
+        num_total = len(self.experiments)
+        num_success_run = 0
+        num_failed = 0
+        num_skipped = 0
+        num_partial_skipped = 0
+        num_partial_run = 0
+        for experiment in self.experiments:
+            # populate display data
+            if experiment["result"]:
+                data_lists_for_display.append(
+                    [
+                        experiment["model_uid"],
+                        experiment["result"].generated_uid,
+                        experiment["result"].metadata["partial"],
+                        experiment["cached"],
+                        experiment["error"],
+                    ]
+                )
+            else:
+                data_lists_for_display.append(
+                    [experiment["model_uid"], "", "", "", experiment["error"]]
+                )
+
+            # statistics
+            if experiment["error"]:
+                num_failed += 1
+            elif experiment["cached"]:
+                num_skipped += 1
+                if experiment["result"].metadata["partial"]:
+                    num_partial_skipped += 1
+            elif experiment["result"]:
+                num_success_run += 1
+                if experiment["result"].metadata["partial"]:
+                    num_partial_run += 1
+
+        tab = tabulate(data_lists_for_display, headers=headers)
+
+        msg = f"Total number of models: {num_total}\n"
+        msg += f"\t{num_skipped} were skipped (already executed), "
+        msg += f"of which {num_partial_run} have partial results\n"
+        msg += f"\t{num_failed} failed\n"
+        msg += f"\t{num_success_run} ran successfully, "
+        msg += f"of which {num_partial_run} have partial results\n"
+
+        config.ui.print(tab)
+        config.ui.print(msg)
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(benchmark_uid, data_uid, models_uids=None, models_input_file=None, ignore_model_errors=False, ignore_failed_experiments=False, no_cache=False, show_summary=False) + + + classmethod + + +

+ + +
+ +

Benchmark execution flow.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

UID of the desired benchmark

+
+
+ required +
data_uid + str + +
+

Registered Dataset UID

+
+
+ required +
models_uids + List | None + +
+

list of model UIDs to execute. + if None, models_input_file will be used

+
+
+ None +
models_input_file + Optional[str] + +
+

filename to read from

+
+
+ None +
+ +
+ Source code in cli/medperf/commands/result/create.py +
20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
@classmethod
+def run(
+    cls,
+    benchmark_uid: int,
+    data_uid: int,
+    models_uids: Optional[List[int]] = None,
+    models_input_file: Optional[str] = None,
+    ignore_model_errors=False,
+    ignore_failed_experiments=False,
+    no_cache=False,
+    show_summary=False,
+):
+    """Benchmark execution flow.
+
+    Args:
+        benchmark_uid (int): UID of the desired benchmark
+        data_uid (str): Registered Dataset UID
+        models_uids (List|None): list of model UIDs to execute.
+                                if None, models_input_file will be used
+        models_input_file: filename to read from
+        if models_uids and models_input_file are None, use all benchmark models
+    """
+    execution = cls(
+        benchmark_uid,
+        data_uid,
+        models_uids,
+        models_input_file,
+        ignore_model_errors,
+        ignore_failed_experiments,
+    )
+    execution.prepare()
+    execution.validate()
+    execution.prepare_models()
+    if not no_cache:
+        execution.load_cached_results()
+    with execution.ui.interactive():
+        results = execution.run_experiments()
+    if show_summary:
+        execution.print_summary()
+    return results
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/result/result/index.html b/reference/commands/result/result/index.html new file mode 100644 index 000000000..fbbe7b55f --- /dev/null +++ b/reference/commands/result/result/index.html @@ -0,0 +1,1210 @@ + + + + + + + + + + + + + + + + + + + + Result - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Result

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+create(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='UID of the desired benchmark'), data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), model_uid=typer.Option(..., '--model_uid', '-m', help='UID of model to execute'), ignore_model_errors=typer.Option(False, '--ignore-model-errors', help='Ignore failing model cubes, allowing for possibly submitting partial results'), no_cache=typer.Option(False, '--no-cache', help='Execute even if results already exist')) + +

+ + +
+ +

Runs the benchmark execution step for a given benchmark, prepared dataset and model

+ +
+ Source code in cli/medperf/commands/result/result.py +
15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
@app.command("create")
+@clean_except
+def create(
+    benchmark_uid: int = typer.Option(
+        ..., "--benchmark", "-b", help="UID of the desired benchmark"
+    ),
+    data_uid: int = typer.Option(
+        ..., "--data_uid", "-d", help="Registered Dataset UID"
+    ),
+    model_uid: int = typer.Option(
+        ..., "--model_uid", "-m", help="UID of model to execute"
+    ),
+    ignore_model_errors: bool = typer.Option(
+        False,
+        "--ignore-model-errors",
+        help="Ignore failing model cubes, allowing for possibly submitting partial results",
+    ),
+    no_cache: bool = typer.Option(
+        False,
+        "--no-cache",
+        help="Execute even if results already exist",
+    ),
+):
+    """Runs the benchmark execution step for a given benchmark, prepared dataset and model"""
+    BenchmarkExecution.run(
+        benchmark_uid,
+        data_uid,
+        [model_uid],
+        no_cache=no_cache,
+        ignore_model_errors=ignore_model_errors,
+    )
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+list(local=typer.Option(False, '--local', help='Get local results'), mine=typer.Option(False, '--mine', help='Get current-user results'), benchmark=typer.Option(None, '--benchmark', '-b', help='Get results for a given benchmark')) + +

+ + +
+ +

List results stored locally and remotely from the user

+ +
+ Source code in cli/medperf/commands/result/result.py +
62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
@app.command("ls")
+@clean_except
+def list(
+    local: bool = typer.Option(False, "--local", help="Get local results"),
+    mine: bool = typer.Option(False, "--mine", help="Get current-user results"),
+    benchmark: int = typer.Option(
+        None, "--benchmark", "-b", help="Get results for a given benchmark"
+    ),
+):
+    """List results stored locally and remotely from the user"""
+    EntityList.run(
+        Result,
+        fields=["UID", "Benchmark", "Model", "Dataset", "Registered"],
+        local_only=local,
+        mine_only=mine,
+        benchmark=benchmark,
+    )
+
+
+
+ +
+ + +
+ + + +

+submit(result_uid=typer.Option(..., '--result', '-r', help='Unregistered result UID'), approval=typer.Option(False, '-y', help='Skip approval step')) + +

+ + +
+ +

Submits already obtained results to the server

+ +
+ Source code in cli/medperf/commands/result/result.py +
49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
@app.command("submit")
+@clean_except
+def submit(
+    result_uid: str = typer.Option(
+        ..., "--result", "-r", help="Unregistered result UID"
+    ),
+    approval: bool = typer.Option(False, "-y", help="Skip approval step"),
+):
+    """Submits already obtained results to the server"""
+    ResultSubmission.run(result_uid, approved=approval)
+    config.ui.print("✅ Done!")
+
+
+
+ +
+ + +
+ + + +

+view(entity_id=typer.Argument(None, help='Result ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local results if result ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user results if result ID is not provided'), benchmark=typer.Option(None, '--benchmark', '-b', help='Get results for a given benchmark'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed')) + +

+ + +
+ +

Displays the information of one or more results

+ +
+ Source code in cli/medperf/commands/result/result.py +
@app.command("view")
+@clean_except
+def view(
+    entity_id: Optional[str] = typer.Argument(None, help="Result ID"),
+    format: str = typer.Option(
+        "yaml",
+        "-f",
+        "--format",
+        help="Format to display contents. Available formats: [yaml, json]",
+    ),
+    local: bool = typer.Option(
+        False, "--local", help="Display local results if result ID is not provided"
+    ),
+    mine: bool = typer.Option(
+        False,
+        "--mine",
+        help="Display current-user results if result ID is not provided",
+    ),
+    benchmark: int = typer.Option(
+        None, "--benchmark", "-b", help="Get results for a given benchmark"
+    ),
+    output: str = typer.Option(
+        None,
+        "--output",
+        "-o",
+        help="Output file to store contents. If not provided, the output will be displayed",
+    ),
+):
+    """Displays the information of one or more results"""
+    EntityView.run(entity_id, Result, format, local, mine, output, benchmark=benchmark)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/result/submit/index.html b/reference/commands/result/submit/index.html new file mode 100644 index 000000000..81b3139db --- /dev/null +++ b/reference/commands/result/submit/index.html @@ -0,0 +1,1154 @@ + + + + + + + + + + + + + + + + + + + + Submit - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Submit

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ ResultSubmission + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/result/submit.py +
10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
class ResultSubmission:
+    @classmethod
+    def run(cls, result_uid, approved=False):
+        sub = cls(result_uid, approved=approved)
+        updated_result_dict = sub.upload_results()
+        sub.to_permanent_path(updated_result_dict)
+        sub.write(updated_result_dict)
+
+    def __init__(self, result_uid, approved=False):
+        self.result_uid = result_uid
+        self.comms = config.comms
+        self.ui = config.ui
+        self.approved = approved
+
+    def request_approval(self, result):
+        if result.approval_status == Status.APPROVED:
+            return True
+
+        dict_pretty_print(result.results)
+        self.ui.print("Above are the results generated by the model")
+
+        approved = approval_prompt(
+            "Do you approve uploading the presented results to the MLCommons comms? [Y/n]"
+        )
+
+        return approved
+
+    def upload_results(self):
+        result = Result.get(self.result_uid)
+        approved = self.approved or self.request_approval(result)
+
+        if not approved:
+            raise CleanExit("Results upload operation cancelled")
+
+        updated_result_dict = result.upload()
+        return updated_result_dict
+
+    def to_permanent_path(self, result_dict: dict):
+        """Rename the temporary result submission to a permanent one
+
+        Args:
+            result_dict (dict): updated results dictionary
+        """
+        result = Result(**result_dict)
+        result_storage = config.results_folder
+        old_res_loc = os.path.join(result_storage, result.generated_uid)
+        new_res_loc = result.path
+        remove_path(new_res_loc)
+        os.rename(old_res_loc, new_res_loc)
+
+    def write(self, updated_result_dict):
+        result = Result(**updated_result_dict)
+        result.write()
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+to_permanent_path(result_dict) + +

+ + +
+ +

Rename the temporary result submission to a permanent one

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
result_dict + dict + +
+

updated results dictionary

+
+
+ required +
+ +
+ Source code in cli/medperf/commands/result/submit.py +
47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
def to_permanent_path(self, result_dict: dict):
+    """Rename the temporary result submission to a permanent one
+
+    Args:
+        result_dict (dict): updated results dictionary
+    """
+    result = Result(**result_dict)
+    result_storage = config.results_folder
+    old_res_loc = os.path.join(result_storage, result.generated_uid)
+    new_res_loc = result.path
+    remove_path(new_res_loc)
+    os.rename(old_res_loc, new_res_loc)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/storage/index.html b/reference/commands/storage/index.html new file mode 100644 index 000000000..de11c2985 --- /dev/null +++ b/reference/commands/storage/index.html @@ -0,0 +1,1088 @@ + + + + + + + + + + + + + + + + + + + + Storage - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Storage

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+clean() + +

+ + +
+ +

Cleans up clutter paths

+ +
+ Source code in cli/medperf/commands/storage.py +
37
+38
+39
+40
+41
+42
+43
@app.command("cleanup")
+def clean():
+    """Cleans up clutter paths"""
+
+    # Force cleanup to be true
+    config.cleanup = True
+    cleanup()
+
+
+
+ +
+ + +
+ + + +

+ls() + +

+ + +
+ +

Show the location of the current medperf assets

+ +
+ Source code in cli/medperf/commands/storage.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
@app.command("ls")
+@clean_except
+def ls():
+    """Show the location of the current medperf assets"""
+    headers = ["Asset", "Location"]
+    info = []
+    for folder in config.storage:
+        info.append((folder, config.storage[folder]["base"]))
+
+    tab = tabulate(info, headers=headers)
+    config.ui.print(tab)
+
+
+
+ +
+ + +
+ + + +

+move(path=typer.Option(..., '--target', '-t', help='Target path')) + +

+ + +
+ +

Moves all storage folders to a target base path. Folders include: +Benchmarks, datasets, mlcubes, results, tests, ...

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
path + str + +
+

target path

+
+
+ typer.Option(..., '--target', '-t', help='Target path') +
+ +
+ Source code in cli/medperf/commands/storage.py +
25
+26
+27
+28
+29
+30
+31
+32
+33
+34
@app.command("move")
+@clean_except
+def move(path: str = typer.Option(..., "--target", "-t", help="Target path")):
+    """Moves all storage folders to a target base path. Folders include:
+    Benchmarks, datasets, mlcubes, results, tests, ...
+
+    Args:
+        path (str): target path
+    """
+    move_storage(path)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/commands/view/index.html b/reference/commands/view/index.html new file mode 100644 index 000000000..e46c3c600 --- /dev/null +++ b/reference/commands/view/index.html @@ -0,0 +1,1338 @@ + + + + + + + + + + + + + + + + + + + + View - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

View

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ EntityView + + +

+ + +
+ + +
+ Source code in cli/medperf/commands/view.py +
11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
class EntityView:
+    @staticmethod
+    def run(
+        entity_id: Union[int, str],
+        entity_class: Entity,
+        format: str = "yaml",
+        local_only: bool = False,
+        mine_only: bool = False,
+        output: str = None,
+        **kwargs,
+    ):
+        """Displays the contents of a single or multiple entities of a given type
+
+        Args:
+            entity_id (Union[int, str]): Entity identifies
+            entity_class (Entity): Entity type
+            local_only (bool, optional): Display all local entities. Defaults to False.
+            mine_only (bool, optional): Display all current-user entities. Defaults to False.
+            format (str, optional): What format to use to display the contents. Valid formats: [yaml, json]. Defaults to yaml.
+            output (str, optional): Path to a file for storing the entity contents. If not provided, the contents are printed.
+            kwargs (dict): Additional parameters for filtering entity lists.
+        """
+        entity_view = EntityView(
+            entity_id, entity_class, format, local_only, mine_only, output, **kwargs
+        )
+        entity_view.validate()
+        entity_view.prepare()
+        if output is None:
+            entity_view.display()
+        else:
+            entity_view.store()
+
+    def __init__(
+        self, entity_id, entity_class, format, local_only, mine_only, output, **kwargs
+    ):
+        self.entity_id = entity_id
+        self.entity_class = entity_class
+        self.format = format
+        self.local_only = local_only
+        self.mine_only = mine_only
+        self.output = output
+        self.filters = kwargs
+        self.data = []
+
+    def validate(self):
+        valid_formats = set(["yaml", "json"])
+        if self.format not in valid_formats:
+            raise InvalidArgumentError("The provided format is not supported")
+
+    def prepare(self):
+        if self.entity_id is not None:
+            entities = [self.entity_class.get(self.entity_id)]
+        else:
+            if self.mine_only:
+                self.filters["owner"] = get_medperf_user_data()["id"]
+
+            entities = self.entity_class.all(
+                local_only=self.local_only, filters=self.filters
+            )
+        self.data = [entity.todict() for entity in entities]
+
+        if self.entity_id is not None:
+            # User expects a single entity if id provided
+            # Don't output the view as a list of entities
+            self.data = self.data[0]
+
+    def display(self):
+        if self.format == "json":
+            formatter = json.dumps
+        if self.format == "yaml":
+            formatter = yaml.dump
+
+        formatted_data = formatter(self.data)
+        config.ui.print(formatted_data)
+
+    def store(self):
+        if self.format == "json":
+            formatter = json.dump
+        if self.format == "yaml":
+            formatter = yaml.dump
+
+        with open(self.output, "w") as f:
+            formatter(self.data, f)
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+run(entity_id, entity_class, format='yaml', local_only=False, mine_only=False, output=None, **kwargs) + + + staticmethod + + +

+ + +
+ +

Displays the contents of a single or multiple entities of a given type

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
entity_id + Union[int, str] + +
+

Entity identifies

+
+
+ required +
entity_class + Entity + +
+

Entity type

+
+
+ required +
local_only + bool + +
+

Display all local entities. Defaults to False.

+
+
+ False +
mine_only + bool + +
+

Display all current-user entities. Defaults to False.

+
+
+ False +
format + str + +
+

What format to use to display the contents. Valid formats: [yaml, json]. Defaults to yaml.

+
+
+ 'yaml' +
output + str + +
+

Path to a file for storing the entity contents. If not provided, the contents are printed.

+
+
+ None +
kwargs + dict + +
+

Additional parameters for filtering entity lists.

+
+
+ {} +
+ +
+ Source code in cli/medperf/commands/view.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
@staticmethod
+def run(
+    entity_id: Union[int, str],
+    entity_class: Entity,
+    format: str = "yaml",
+    local_only: bool = False,
+    mine_only: bool = False,
+    output: str = None,
+    **kwargs,
+):
+    """Displays the contents of a single or multiple entities of a given type
+
+    Args:
+        entity_id (Union[int, str]): Entity identifies
+        entity_class (Entity): Entity type
+        local_only (bool, optional): Display all local entities. Defaults to False.
+        mine_only (bool, optional): Display all current-user entities. Defaults to False.
+        format (str, optional): What format to use to display the contents. Valid formats: [yaml, json]. Defaults to yaml.
+        output (str, optional): Path to a file for storing the entity contents. If not provided, the contents are printed.
+        kwargs (dict): Additional parameters for filtering entity lists.
+    """
+    entity_view = EntityView(
+        entity_id, entity_class, format, local_only, mine_only, output, **kwargs
+    )
+    entity_view.validate()
+    entity_view.prepare()
+    if output is None:
+        entity_view.display()
+    else:
+        entity_view.store()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/auth/auth0/index.html b/reference/comms/auth/auth0/index.html new file mode 100644 index 000000000..87ad07c02 --- /dev/null +++ b/reference/comms/auth/auth0/index.html @@ -0,0 +1,2284 @@ + + + + + + + + + + + + + + + + + + + + Auth0 - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Auth0

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Auth0 + + +

+ + +
+

+ Bases: Auth

+ + +
+ Source code in cli/medperf/comms/auth/auth0.py +
class Auth0(Auth):
+    def __init__(self):
+        self.domain = config.auth_domain
+        self.client_id = config.auth_client_id
+        self.audience = config.auth_audience
+        self._lock = threading.Lock()
+
+    def login(self, email):
+        """Retrieves and stores an access token/refresh token pair from the auth0
+        backend using the device authorization flow.
+
+        Args:
+            email (str): user email. This will be used to validate that the received
+                         id_token contains the same email address.
+
+        """
+        device_code_response = self.__request_device_code()
+
+        device_code = device_code_response["device_code"]
+        user_code = device_code_response["user_code"]
+        verification_uri_complete = device_code_response["verification_uri_complete"]
+        interval = device_code_response["interval"]
+
+        config.ui.print(
+            "\nPlease go to the following link to complete your login request:\n"
+            f"\t{verification_uri_complete}\n\n"
+            "Make sure that you will be presented with the following code:\n"
+            f"\t{user_code}\n\n"
+        )
+        config.ui.print_warning(
+            "Keep this terminal open until you complete your login request. "
+            "The command will exit on its own once you complete the request. "
+            "If you wish to stop the login request anyway, press Ctrl+C."
+        )
+        token_response, token_issued_at = self.__get_device_access_token(
+            device_code, interval
+        )
+        access_token = token_response["access_token"]
+        id_token = token_response["id_token"]
+        refresh_token = token_response["refresh_token"]
+        token_expires_in = token_response["expires_in"]
+
+        id_token_payload = verify_token(id_token)
+        self.__check_token_email(id_token_payload, email)
+
+        set_credentials(
+            access_token,
+            refresh_token,
+            id_token_payload,
+            token_issued_at,
+            token_expires_in,
+        )
+
+    def __request_device_code(self):
+        """Get a device code from the auth0 backend to be used for the authorization process"""
+        url = f"https://{self.domain}/oauth/device/code"
+        headers = {"content-type": "application/x-www-form-urlencoded"}
+        body = {
+            "client_id": self.client_id,
+            "audience": self.audience,
+            "scope": "offline_access openid email",
+        }
+        res = requests.post(url=url, headers=headers, data=body)
+
+        if res.status_code != 200:
+            self.__raise_errors(res, "Login")
+
+        return res.json()
+
+    def __get_device_access_token(self, device_code, polling_interval):
+        """Get the access token from the auth0 backend associated with
+        the device code requested before. This function will keep polling
+        the access token until the user completes the browser flow part
+        of the authorization process.
+
+        Args:
+            device_code (str): A temporary device code requested by `__request_device_code`
+            polling_interval (float): number of seconds to wait between each two polling requests
+
+        Returns:
+            json_res (dict): the response of the successful request, containg the access/refresh tokens pair
+            token_issued_at (float): the timestamp when the access token was issued
+        """
+        url = f"https://{self.domain}/oauth/token"
+        headers = {"content-type": "application/x-www-form-urlencoded"}
+        body = {
+            "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
+            "device_code": device_code,
+            "client_id": self.client_id,
+        }
+
+        while True:
+            time.sleep(polling_interval)
+            token_issued_at = time.time()
+            res = requests.post(url=url, headers=headers, data=body)
+            if res.status_code == 200:
+                json_res = res.json()
+                return json_res, token_issued_at
+
+            try:
+                json_res = res.json()
+            except requests.exceptions.JSONDecodeError:
+                json_res = {}
+            error = json_res.get("error", None)
+            if error not in ["slow_down", "authorization_pending"]:
+                self.__raise_errors(res, "Login")
+
+    def __check_token_email(self, id_token_payload, email):
+        """Checks if the email provided by the user in the terminal matches the
+        email found in the recieved id token."""
+        email_in_token = id_token_payload["email"]
+        if email.lower() != email_in_token:
+            raise CommunicationError(
+                "The email provided in the terminal does not match the one provided during login"
+            )
+
+    def logout(self):
+        """Logs out the user by revoking their refresh token and deleting the
+        stored tokens."""
+
+        creds = read_credentials()
+        refresh_token = creds["refresh_token"]
+
+        url = f"https://{self.domain}/oauth/revoke"
+        headers = {"content-type": "application/json"}
+        body = {
+            "client_id": self.client_id,
+            "token": refresh_token,
+        }
+        res = requests.post(url=url, headers=headers, json=body)
+
+        if res.status_code != 200:
+            self.__raise_errors(res, "Logout")
+
+        delete_credentials()
+
+    @property
+    def access_token(self):
+        """Thread and process-safe access token retrieval"""
+        # In case of multiple threads are using the same connection object,
+        # keep the thread lock, otherwise the database will throw
+        # errors of starting a transaction within a transaction.
+        # In case of each thread is using a different connection object,
+        # keep the thread lock to avoid the OperationalError when
+        # multiple threads want to access the database.
+        with self._lock:
+            # TODO: This is temporary. Use a cleaner solution.
+            db = sqlite3.connect(config.tokens_db, isolation_level=None, timeout=60)
+            try:
+                db.execute("BEGIN EXCLUSIVE TRANSACTION")
+            except sqlite3.OperationalError:
+                msg = "Another process is using the database. Try again later"
+                raise CommunicationError(msg)
+            token = self._access_token
+            # Sqlite will automatically execute COMMIT and close the connection
+            # if an exception is raised during the retrieval of the access token.
+            db.execute("COMMIT")
+            db.close()
+
+            return token
+
+    @property
+    def _access_token(self):
+        """Reads and returns an access token of the currently logged
+        in user to be used for authorizing requests to the MedPerf server.
+        Refresh the token if necessary.
+
+        Returns:
+            access_token (str): the access token
+        """
+
+        creds = read_credentials()
+        access_token = creds["access_token"]
+        refresh_token = creds["refresh_token"]
+        token_expires_in = creds["token_expires_in"]
+        token_issued_at = creds["token_issued_at"]
+        if (
+            time.time()
+            > token_issued_at + token_expires_in - config.token_expiration_leeway
+        ):
+            access_token = self.__refresh_access_token(refresh_token)
+        return access_token
+
+    def __refresh_access_token(self, refresh_token):
+        """Retrieve and store a new access token using a refresh token.
+        A new refresh token will also be retrieved and stored.
+
+        Args:
+            refresh_token (str): the refresh token
+        Returns:
+            access_token (str): the new access token
+        """
+
+        url = f"https://{self.domain}/oauth/token"
+        headers = {"content-type": "application/x-www-form-urlencoded"}
+        body = {
+            "grant_type": "refresh_token",
+            "client_id": self.client_id,
+            "refresh_token": refresh_token,
+        }
+        token_issued_at = time.time()
+        logging.debug("Refreshing access token.")
+        res = requests.post(url=url, headers=headers, data=body)
+
+        if res.status_code != 200:
+            self.__raise_errors(res, "Token refresh")
+
+        json_res = res.json()
+
+        access_token = json_res["access_token"]
+        id_token = json_res["id_token"]
+        refresh_token = json_res["refresh_token"]
+        token_expires_in = json_res["expires_in"]
+
+        id_token_payload = verify_token(id_token)
+        set_credentials(
+            access_token,
+            refresh_token,
+            id_token_payload,
+            token_issued_at,
+            token_expires_in,
+        )
+
+        return access_token
+
+    def __raise_errors(self, res, action):
+        """log the failed request's response and raise errors.
+
+        Args:
+            res (requests.Response): the response of a failed request
+            action (str): a string for more informative error display
+            to the user.
+        """
+
+        log_response_error(res)
+        if res.status_code == 429:
+            raise CommunicationError("Too many requests. Try again later.")
+
+        try:
+            json_res = res.json()
+        except requests.exceptions.JSONDecodeError:
+            json_res = {}
+
+        description = json_res.get("error_description", "")
+        msg = f"{action} failed."
+        if description:
+            msg += f" {description}"
+        raise CommunicationError(msg)
+
+
+ + + +
+ + + + + + + +
+ + + +

+access_token + + + property + + +

+ + +
+ +

Thread and process-safe access token retrieval

+
+ +
+ + + + +
+ + + +

+__check_token_email(id_token_payload, email) + +

+ + +
+ +

Checks if the email provided by the user in the terminal matches the +email found in the recieved id token.

+ +
+ Source code in cli/medperf/comms/auth/auth0.py +
def __check_token_email(self, id_token_payload, email):
+    """Checks if the email provided by the user in the terminal matches the
+    email found in the recieved id token."""
+    email_in_token = id_token_payload["email"]
+    if email.lower() != email_in_token:
+        raise CommunicationError(
+            "The email provided in the terminal does not match the one provided during login"
+        )
+
+
+
+ +
+ + +
+ + + +

+__get_device_access_token(device_code, polling_interval) + +

+ + +
+ +

Get the access token from the auth0 backend associated with +the device code requested before. This function will keep polling +the access token until the user completes the browser flow part +of the authorization process.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
device_code + str + +
+

A temporary device code requested by __request_device_code

+
+
+ required +
polling_interval + float + +
+

number of seconds to wait between each two polling requests

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + + + + + +
Name TypeDescription
json_res + dict + +
+

the response of the successful request, containg the access/refresh tokens pair

+
+
token_issued_at + float + +
+

the timestamp when the access token was issued

+
+
+ +
+ Source code in cli/medperf/comms/auth/auth0.py +
def __get_device_access_token(self, device_code, polling_interval):
+    """Get the access token from the auth0 backend associated with
+    the device code requested before. This function will keep polling
+    the access token until the user completes the browser flow part
+    of the authorization process.
+
+    Args:
+        device_code (str): A temporary device code requested by `__request_device_code`
+        polling_interval (float): number of seconds to wait between each two polling requests
+
+    Returns:
+        json_res (dict): the response of the successful request, containg the access/refresh tokens pair
+        token_issued_at (float): the timestamp when the access token was issued
+    """
+    url = f"https://{self.domain}/oauth/token"
+    headers = {"content-type": "application/x-www-form-urlencoded"}
+    body = {
+        "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
+        "device_code": device_code,
+        "client_id": self.client_id,
+    }
+
+    while True:
+        time.sleep(polling_interval)
+        token_issued_at = time.time()
+        res = requests.post(url=url, headers=headers, data=body)
+        if res.status_code == 200:
+            json_res = res.json()
+            return json_res, token_issued_at
+
+        try:
+            json_res = res.json()
+        except requests.exceptions.JSONDecodeError:
+            json_res = {}
+        error = json_res.get("error", None)
+        if error not in ["slow_down", "authorization_pending"]:
+            self.__raise_errors(res, "Login")
+
+
+
+ +
+ + +
+ + + +

+__raise_errors(res, action) + +

+ + +
+ +

log the failed request's response and raise errors.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
res + requests.Response + +
+

the response of a failed request

+
+
+ required +
action + str + +
+

a string for more informative error display

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/auth/auth0.py +
def __raise_errors(self, res, action):
+    """log the failed request's response and raise errors.
+
+    Args:
+        res (requests.Response): the response of a failed request
+        action (str): a string for more informative error display
+        to the user.
+    """
+
+    log_response_error(res)
+    if res.status_code == 429:
+        raise CommunicationError("Too many requests. Try again later.")
+
+    try:
+        json_res = res.json()
+    except requests.exceptions.JSONDecodeError:
+        json_res = {}
+
+    description = json_res.get("error_description", "")
+    msg = f"{action} failed."
+    if description:
+        msg += f" {description}"
+    raise CommunicationError(msg)
+
+
+
+ +
+ + +
+ + + +

+__refresh_access_token(refresh_token) + +

+ + +
+ +

Retrieve and store a new access token using a refresh token. +A new refresh token will also be retrieved and stored.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
refresh_token + str + +
+

the refresh token

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
access_token + str + +
+

the new access token

+
+
+ +
+ Source code in cli/medperf/comms/auth/auth0.py +
def __refresh_access_token(self, refresh_token):
+    """Retrieve and store a new access token using a refresh token.
+    A new refresh token will also be retrieved and stored.
+
+    Args:
+        refresh_token (str): the refresh token
+    Returns:
+        access_token (str): the new access token
+    """
+
+    url = f"https://{self.domain}/oauth/token"
+    headers = {"content-type": "application/x-www-form-urlencoded"}
+    body = {
+        "grant_type": "refresh_token",
+        "client_id": self.client_id,
+        "refresh_token": refresh_token,
+    }
+    token_issued_at = time.time()
+    logging.debug("Refreshing access token.")
+    res = requests.post(url=url, headers=headers, data=body)
+
+    if res.status_code != 200:
+        self.__raise_errors(res, "Token refresh")
+
+    json_res = res.json()
+
+    access_token = json_res["access_token"]
+    id_token = json_res["id_token"]
+    refresh_token = json_res["refresh_token"]
+    token_expires_in = json_res["expires_in"]
+
+    id_token_payload = verify_token(id_token)
+    set_credentials(
+        access_token,
+        refresh_token,
+        id_token_payload,
+        token_issued_at,
+        token_expires_in,
+    )
+
+    return access_token
+
+
+
+ +
+ + +
+ + + +

+__request_device_code() + +

+ + +
+ +

Get a device code from the auth0 backend to be used for the authorization process

+ +
+ Source code in cli/medperf/comms/auth/auth0.py +
71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
def __request_device_code(self):
+    """Get a device code from the auth0 backend to be used for the authorization process"""
+    url = f"https://{self.domain}/oauth/device/code"
+    headers = {"content-type": "application/x-www-form-urlencoded"}
+    body = {
+        "client_id": self.client_id,
+        "audience": self.audience,
+        "scope": "offline_access openid email",
+    }
+    res = requests.post(url=url, headers=headers, data=body)
+
+    if res.status_code != 200:
+        self.__raise_errors(res, "Login")
+
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+login(email) + +

+ + +
+ +

Retrieves and stores an access token/refresh token pair from the auth0 +backend using the device authorization flow.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
email + str + +
+

user email. This will be used to validate that the received + id_token contains the same email address.

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/auth/auth0.py +
25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
def login(self, email):
+    """Retrieves and stores an access token/refresh token pair from the auth0
+    backend using the device authorization flow.
+
+    Args:
+        email (str): user email. This will be used to validate that the received
+                     id_token contains the same email address.
+
+    """
+    device_code_response = self.__request_device_code()
+
+    device_code = device_code_response["device_code"]
+    user_code = device_code_response["user_code"]
+    verification_uri_complete = device_code_response["verification_uri_complete"]
+    interval = device_code_response["interval"]
+
+    config.ui.print(
+        "\nPlease go to the following link to complete your login request:\n"
+        f"\t{verification_uri_complete}\n\n"
+        "Make sure that you will be presented with the following code:\n"
+        f"\t{user_code}\n\n"
+    )
+    config.ui.print_warning(
+        "Keep this terminal open until you complete your login request. "
+        "The command will exit on its own once you complete the request. "
+        "If you wish to stop the login request anyway, press Ctrl+C."
+    )
+    token_response, token_issued_at = self.__get_device_access_token(
+        device_code, interval
+    )
+    access_token = token_response["access_token"]
+    id_token = token_response["id_token"]
+    refresh_token = token_response["refresh_token"]
+    token_expires_in = token_response["expires_in"]
+
+    id_token_payload = verify_token(id_token)
+    self.__check_token_email(id_token_payload, email)
+
+    set_credentials(
+        access_token,
+        refresh_token,
+        id_token_payload,
+        token_issued_at,
+        token_expires_in,
+    )
+
+
+
+ +
+ + +
+ + + +

+logout() + +

+ + +
+ +

Logs out the user by revoking their refresh token and deleting the +stored tokens.

+ +
+ Source code in cli/medperf/comms/auth/auth0.py +
def logout(self):
+    """Logs out the user by revoking their refresh token and deleting the
+    stored tokens."""
+
+    creds = read_credentials()
+    refresh_token = creds["refresh_token"]
+
+    url = f"https://{self.domain}/oauth/revoke"
+    headers = {"content-type": "application/json"}
+    body = {
+        "client_id": self.client_id,
+        "token": refresh_token,
+    }
+    res = requests.post(url=url, headers=headers, json=body)
+
+    if res.status_code != 200:
+        self.__raise_errors(res, "Logout")
+
+    delete_credentials()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/auth/interface/index.html b/reference/comms/auth/interface/index.html new file mode 100644 index 000000000..09e040af2 --- /dev/null +++ b/reference/comms/auth/interface/index.html @@ -0,0 +1,1147 @@ + + + + + + + + + + + + + + + + + + + + Interface - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Interface

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Auth + + +

+ + +
+

+ Bases: ABC

+ + +
+ Source code in cli/medperf/comms/auth/interface.py +
 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
class Auth(ABC):
+    @abstractmethod
+    def __init__(self):
+        """Initialize the class"""
+
+    @abstractmethod
+    def login(self, email):
+        """Log in a user"""
+
+    @abstractmethod
+    def logout(self):
+        """Log out a user"""
+
+    @property
+    @abstractmethod
+    def access_token(self):
+        """An access token to authorize requests to the MedPerf server"""
+
+
+ + + +
+ + + + + + + +
+ + + +

+access_token + + + abstractmethod + property + + +

+ + +
+ +

An access token to authorize requests to the MedPerf server

+
+ +
+ + + + +
+ + + +

+__init__() + + + abstractmethod + + +

+ + +
+ +

Initialize the class

+ +
+ Source code in cli/medperf/comms/auth/interface.py +
5
+6
+7
@abstractmethod
+def __init__(self):
+    """Initialize the class"""
+
+
+
+ +
+ + +
+ + + +

+login(email) + + + abstractmethod + + +

+ + +
+ +

Log in a user

+ +
+ Source code in cli/medperf/comms/auth/interface.py +
 9
+10
+11
@abstractmethod
+def login(self, email):
+    """Log in a user"""
+
+
+
+ +
+ + +
+ + + +

+logout() + + + abstractmethod + + +

+ + +
+ +

Log out a user

+ +
+ Source code in cli/medperf/comms/auth/interface.py +
13
+14
+15
@abstractmethod
+def logout(self):
+    """Log out a user"""
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/auth/local/index.html b/reference/comms/auth/local/index.html new file mode 100644 index 000000000..3a38ba600 --- /dev/null +++ b/reference/comms/auth/local/index.html @@ -0,0 +1,1262 @@ + + + + + + + + + + + + + + + + + + + + Local - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Local

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Local + + +

+ + +
+

+ Bases: Auth

+ + +
+ Source code in cli/medperf/comms/auth/local.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
class Local(Auth):
+    def __init__(self):
+        with open(config.local_tokens_path) as f:
+            self.tokens = json.load(f)
+
+    def login(self, email):
+        """Retrieves and stores an access token from a local store json file.
+
+        Args:
+            email (str): user email.
+        """
+
+        try:
+            access_token = self.tokens[email]
+        except KeyError:
+            raise InvalidArgumentError(
+                "The provided email does not exist for testing. "
+                "Make sure you activated the right profile."
+            )
+        refresh_token = "refresh token"
+        id_token_payload = {"email": email}
+        token_issued_at = 0
+        token_expires_in = 10**10
+
+        set_credentials(
+            access_token,
+            refresh_token,
+            id_token_payload,
+            token_issued_at,
+            token_expires_in,
+        )
+
+    def logout(self):
+        """Logs out the user by deleting the stored tokens."""
+        delete_credentials()
+
+    @property
+    def access_token(self):
+        """Reads and returns an access token of the currently logged
+        in user to be used for authorizing requests to the MedPerf server.
+
+        Returns:
+            access_token (str): the access token
+        """
+
+        creds = read_credentials()
+        access_token = creds["access_token"]
+        return access_token
+
+
+ + + +
+ + + + + + + +
+ + + +

+access_token + + + property + + +

+ + +
+ +

Reads and returns an access token of the currently logged +in user to be used for authorizing requests to the MedPerf server.

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
access_token + str + +
+

the access token

+
+
+
+ +
+ + + + +
+ + + +

+login(email) + +

+ + +
+ +

Retrieves and stores an access token from a local store json file.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
email + str + +
+

user email.

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/auth/local.py +
17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
def login(self, email):
+    """Retrieves and stores an access token from a local store json file.
+
+    Args:
+        email (str): user email.
+    """
+
+    try:
+        access_token = self.tokens[email]
+    except KeyError:
+        raise InvalidArgumentError(
+            "The provided email does not exist for testing. "
+            "Make sure you activated the right profile."
+        )
+    refresh_token = "refresh token"
+    id_token_payload = {"email": email}
+    token_issued_at = 0
+    token_expires_in = 10**10
+
+    set_credentials(
+        access_token,
+        refresh_token,
+        id_token_payload,
+        token_issued_at,
+        token_expires_in,
+    )
+
+
+
+ +
+ + +
+ + + +

+logout() + +

+ + +
+ +

Logs out the user by deleting the stored tokens.

+ +
+ Source code in cli/medperf/comms/auth/local.py +
44
+45
+46
def logout(self):
+    """Logs out the user by deleting the stored tokens."""
+    delete_credentials()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/auth/token_verifier/index.html b/reference/comms/auth/token_verifier/index.html new file mode 100644 index 000000000..2f3133955 --- /dev/null +++ b/reference/comms/auth/token_verifier/index.html @@ -0,0 +1,919 @@ + + + + + + + + + + + + + + + + + + + + Token verifier - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Token verifier

+ +
+ + + +
+ +

This module defines a wrapper around the existing token verifier in auth0-python library. +The library is designed to cache public keys in memory. Since our client is ephemeral, we +wrapped the library's JwksFetcher to cache keys in the filesystem storage, and wrapped the +library's signature verifier to use this new JwksFetcher

+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/entity_resources/resources/index.html b/reference/comms/entity_resources/resources/index.html new file mode 100644 index 000000000..d0236f425 --- /dev/null +++ b/reference/comms/entity_resources/resources/index.html @@ -0,0 +1,1585 @@ + + + + + + + + + + + + + + + + + + + + Resources - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Resources

+ +
+ + + +
+ +

This module downloads files from the internet. It provides a set of +functions to download common files that are necessary for workflow executions +and are not on the MedPerf server. An example of such files is model weights +of a Model MLCube.

+

This module takes care of validating the integrity of the downloaded file +if a hash was specified when requesting the file. It also returns the hash +of the downloaded file, which can be the original specified hash or the +calculated hash of the freshly downloaded file if no hash was specified.

+

Additionally, to avoid unnecessary downloads, an existing file +will not be re-downloaded.

+ + + +
+ + + + + + + + + + +
+ + + +

+get_benchmark_demo_dataset(url, expected_hash=None) + +

+ + +
+ +

Downloads and extracts a demo dataset. If the hash is provided, +the file's integrity will be checked upon download.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
url + str + +
+

URL where the compressed demo dataset file can be downloaded.

+
+
+ required +
expected_hash + str + +
+

expected hash of the downloaded file

+
+
+ None +
+ + + +

Returns:

+ + + + + + + + + + + + + + + + + +
Name TypeDescription
output_path + str + +
+

location where the uncompressed demo dataset is stored locally.

+
+
hash_value + str + +
+

The hash of the downloaded tarball file

+
+
+ +
+ Source code in cli/medperf/comms/entity_resources/resources.py +
def get_benchmark_demo_dataset(url: str, expected_hash: str = None) -> str:
+    """Downloads and extracts a demo dataset. If the hash is provided,
+    the file's integrity will be checked upon download.
+
+    Args:
+        url (str): URL where the compressed demo dataset file can be downloaded.
+        expected_hash (str, optional): expected hash of the downloaded file
+
+    Returns:
+        output_path (str): location where the uncompressed demo dataset is stored locally.
+        hash_value (str): The hash of the downloaded tarball file
+    """
+    # TODO: at some point maybe it is better to download demo datasets in
+    # their benchmark folder. Doing this, we should then modify
+    # the compatibility test command and remove the option of directly passing
+    # demo datasets. This would look cleaner.
+    # Possible cons: if multiple benchmarks use the same demo dataset.
+    demo_storage = config.demo_datasets_folder
+    if expected_hash:
+        # If the folder exists, return
+        demo_dataset_folder = os.path.join(demo_storage, expected_hash)
+        if os.path.exists(demo_dataset_folder):
+            return demo_dataset_folder, expected_hash
+
+    # make sure files are uncompressed while in tmp storage, to avoid any clutter
+    # objects if uncompression fails for some reason.
+    tmp_output_folder = generate_tmp_path()
+    output_tarball_path = os.path.join(tmp_output_folder, config.tarball_filename)
+    hash_value = download_resource(url, output_tarball_path, expected_hash)
+
+    untar(output_tarball_path)
+    demo_dataset_folder = os.path.join(demo_storage, hash_value)
+    if os.path.exists(demo_dataset_folder):
+        # handle the possibility of having clutter uncompressed files
+        remove_path(demo_dataset_folder)
+    os.rename(tmp_output_folder, demo_dataset_folder)
+    return demo_dataset_folder, hash_value
+
+
+
+ +
+ + +
+ + + +

+get_cube(url, cube_path, expected_hash=None) + +

+ + +
+ +

Downloads and writes a cube mlcube.yaml file

+ +
+ Source code in cli/medperf/comms/entity_resources/resources.py +
86
+87
+88
+89
def get_cube(url: str, cube_path: str, expected_hash: str = None):
+    """Downloads and writes a cube mlcube.yaml file"""
+    output_path = os.path.join(cube_path, config.cube_filename)
+    return _get_regular_file(url, output_path, expected_hash)
+
+
+
+ +
+ + +
+ + + +

+get_cube_additional(url, cube_path, expected_tarball_hash=None) + +

+ + +
+ +

Retrieves additional files of an MLCube. The additional files +will be in a compressed tarball file. The function will additionally +extract this file.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
url + str + +
+

URL where the additional_files.tar.gz file can be downloaded.

+
+
+ required +
cube_path + str + +
+

Cube location.

+
+
+ required +
expected_tarball_hash + str + +
+

expected hash of tarball file

+
+
+ None +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
tarball_hash + str + +
+

The hash of the downloaded tarball file

+
+
+ +
+ Source code in cli/medperf/comms/entity_resources/resources.py +
def get_cube_additional(
+    url: str,
+    cube_path: str,
+    expected_tarball_hash: str = None,
+) -> str:
+    """Retrieves additional files of an MLCube. The additional files
+    will be in a compressed tarball file. The function will additionally
+    extract this file.
+
+    Args:
+        url (str): URL where the additional_files.tar.gz file can be downloaded.
+        cube_path (str): Cube location.
+        expected_tarball_hash (str, optional): expected hash of tarball file
+
+    Returns:
+        tarball_hash (str): The hash of the downloaded tarball file
+    """
+    additional_files_folder = os.path.join(cube_path, config.additional_path)
+    mlcube_cache_file = os.path.join(cube_path, config.mlcube_cache_file)
+    if not _should_get_cube_additional(
+        additional_files_folder, expected_tarball_hash, mlcube_cache_file
+    ):
+        return expected_tarball_hash
+
+    # Download the additional files. Make sure files are extracted in tmp storage
+    # to avoid any clutter objects if uncompression fails for some reason.
+    tmp_output_folder = generate_tmp_path()
+    output_tarball_path = os.path.join(tmp_output_folder, config.tarball_filename)
+    tarball_hash = download_resource(url, output_tarball_path, expected_tarball_hash)
+
+    untar(output_tarball_path)
+    parent_folder = os.path.dirname(os.path.normpath(additional_files_folder))
+    os.makedirs(parent_folder, exist_ok=True)
+    if os.path.exists(additional_files_folder):
+        # handle the possibility of having clutter uncompressed files
+        remove_path(additional_files_folder)
+    os.rename(tmp_output_folder, additional_files_folder)
+
+    # Store the downloaded tarball hash to be used later for verifying that the
+    # local cache is up to date
+    with open(mlcube_cache_file, "w") as f:  # assumes parent folder already exists
+        contents = {"additional_files_cached_hash": tarball_hash}
+        yaml.dump(contents, f)
+
+    return tarball_hash
+
+
+
+ +
+ + +
+ + + +

+get_cube_image(url, cube_path, hash_value=None) + +

+ + +
+ +

Retrieves and stores the image file from the server. Stores images +on a shared location, and retrieves a cached image by hash if found locally. +Creates a symbolic link to the cube storage.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
url + str + +
+

URL where the image file can be downloaded.

+
+
+ required +
cube_path + str + +
+

Path to cube.

+
+
+ required +
hash_value + (str, Optional) + +
+

File hash to store under shared storage. Defaults to None.

+
+
+ None +
+ + + +

Returns:

+ + + + + + + + + + + + + + + + + +
Name TypeDescription
image_cube_file + str + +
+

Location where the image file is stored locally.

+
+
hash_value + str + +
+

The hash of the downloaded file

+
+
+ +
+ Source code in cli/medperf/comms/entity_resources/resources.py +
def get_cube_image(url: str, cube_path: str, hash_value: str = None) -> str:
+    """Retrieves and stores the image file from the server. Stores images
+    on a shared location, and retrieves a cached image by hash if found locally.
+    Creates a symbolic link to the cube storage.
+
+    Args:
+        url (str): URL where the image file can be downloaded.
+        cube_path (str): Path to cube.
+        hash_value (str, Optional): File hash to store under shared storage. Defaults to None.
+
+    Returns:
+        image_cube_file: Location where the image file is stored locally.
+        hash_value (str): The hash of the downloaded file
+    """
+    image_path = config.image_path
+    image_name = get_cube_image_name(cube_path)
+    image_cube_path = os.path.join(cube_path, image_path)
+    os.makedirs(image_cube_path, exist_ok=True)
+    image_cube_file = os.path.join(image_cube_path, image_name)
+    if os.path.islink(image_cube_file):  # could be a broken link
+        # Remove existing links
+        os.unlink(image_cube_file)
+
+    imgs_storage = config.images_folder
+    if not hash_value:
+        # No hash provided, we need to download the file first
+        tmp_output_path = generate_tmp_path()
+        hash_value = download_resource(url, tmp_output_path)
+        img_storage = os.path.join(imgs_storage, hash_value)
+        shutil.move(tmp_output_path, img_storage)
+    else:
+        img_storage = os.path.join(imgs_storage, hash_value)
+        if not os.path.exists(img_storage):
+            # If image doesn't exist locally, download it normally
+            download_resource(url, img_storage, hash_value)
+
+    # Create a symbolic link to individual cube storage
+    os.symlink(img_storage, image_cube_file)
+    return image_cube_file, hash_value
+
+
+
+ +
+ + +
+ + + +

+get_cube_params(url, cube_path, expected_hash=None) + +

+ + +
+ +

Downloads and writes a cube parameters.yaml file

+ +
+ Source code in cli/medperf/comms/entity_resources/resources.py +
92
+93
+94
+95
def get_cube_params(url: str, cube_path: str, expected_hash: str = None):
+    """Downloads and writes a cube parameters.yaml file"""
+    output_path = os.path.join(cube_path, config.workspace_path, config.params_filename)
+    return _get_regular_file(url, output_path, expected_hash)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/entity_resources/sources/direct/index.html b/reference/comms/entity_resources/sources/direct/index.html new file mode 100644 index 000000000..96a265805 --- /dev/null +++ b/reference/comms/entity_resources/sources/direct/index.html @@ -0,0 +1,1342 @@ + + + + + + + + + + + + + + + + + + + + Direct - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Direct

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ DirectLinkSource + + +

+ + +
+

+ Bases: BaseSource

+ + +
+ Source code in cli/medperf/comms/entity_resources/sources/direct.py +
10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
class DirectLinkSource(BaseSource):
+    prefix = "direct:"
+
+    @classmethod
+    def validate_resource(cls, value: str):
+        """This class expects a resource string of the form
+        `direct:<URL>` or only a URL.
+        Args:
+            resource (str): the resource string
+
+        Returns:
+            (str|None): The URL if the pattern matches, else None
+        """
+        prefix = cls.prefix
+        if value.startswith(prefix):
+            prefix_len = len(prefix)
+            value = value[prefix_len:]
+
+        if validators.url(value):
+            return value
+
+    def __init__(self):
+        pass
+
+    def authenticate(self):
+        pass
+
+    def __download_once(self, resource_identifier: str, output_path: str):
+        """Downloads a direct-download-link file by streaming its contents. source:
+        https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests
+        """
+        with requests.get(resource_identifier, stream=True) as res:
+            if res.status_code != 200:
+                log_response_error(res)
+                msg = (
+                    "There was a problem retrieving the specified file at "
+                    + resource_identifier
+                )
+                raise CommunicationRetrievalError(msg)
+
+            with open(output_path, "wb") as f:
+                for chunk in res.iter_content(chunk_size=config.ddl_stream_chunk_size):
+                    # NOTE: if the response is chunk-encoded, this may not work
+                    # check whether this is common.
+                    f.write(chunk)
+
+    def download(self, resource_identifier: str, output_path: str):
+        """Downloads a direct-download-link file with multiple attempts. This is
+        done due to facing transient network failure from some direct download
+        link servers."""
+
+        attempt = 0
+        while attempt < config.ddl_max_redownload_attempts:
+            try:
+                self.__download_once(resource_identifier, output_path)
+                return
+            except CommunicationRetrievalError:
+                if os.path.exists(output_path):
+                    remove_path(output_path)
+                attempt += 1
+
+        raise CommunicationRetrievalError(f"Could not download {resource_identifier}")
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__download_once(resource_identifier, output_path) + +

+ + +
+ +

Downloads a direct-download-link file by streaming its contents. source: +https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests

+ +
+ Source code in cli/medperf/comms/entity_resources/sources/direct.py +
37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
def __download_once(self, resource_identifier: str, output_path: str):
+    """Downloads a direct-download-link file by streaming its contents. source:
+    https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests
+    """
+    with requests.get(resource_identifier, stream=True) as res:
+        if res.status_code != 200:
+            log_response_error(res)
+            msg = (
+                "There was a problem retrieving the specified file at "
+                + resource_identifier
+            )
+            raise CommunicationRetrievalError(msg)
+
+        with open(output_path, "wb") as f:
+            for chunk in res.iter_content(chunk_size=config.ddl_stream_chunk_size):
+                # NOTE: if the response is chunk-encoded, this may not work
+                # check whether this is common.
+                f.write(chunk)
+
+
+
+ +
+ + +
+ + + +

+download(resource_identifier, output_path) + +

+ + +
+ +

Downloads a direct-download-link file with multiple attempts. This is +done due to facing transient network failure from some direct download +link servers.

+ +
+ Source code in cli/medperf/comms/entity_resources/sources/direct.py +
56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
def download(self, resource_identifier: str, output_path: str):
+    """Downloads a direct-download-link file with multiple attempts. This is
+    done due to facing transient network failure from some direct download
+    link servers."""
+
+    attempt = 0
+    while attempt < config.ddl_max_redownload_attempts:
+        try:
+            self.__download_once(resource_identifier, output_path)
+            return
+        except CommunicationRetrievalError:
+            if os.path.exists(output_path):
+                remove_path(output_path)
+            attempt += 1
+
+    raise CommunicationRetrievalError(f"Could not download {resource_identifier}")
+
+
+
+ +
+ + +
+ + + +

+validate_resource(value) + + + classmethod + + +

+ + +
+ +

This class expects a resource string of the form +direct:<URL> or only a URL.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
resource + str + +
+

the resource string

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ str | None + +
+

The URL if the pattern matches, else None

+
+
+ +
+ Source code in cli/medperf/comms/entity_resources/sources/direct.py +
13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
@classmethod
+def validate_resource(cls, value: str):
+    """This class expects a resource string of the form
+    `direct:<URL>` or only a URL.
+    Args:
+        resource (str): the resource string
+
+    Returns:
+        (str|None): The URL if the pattern matches, else None
+    """
+    prefix = cls.prefix
+    if value.startswith(prefix):
+        prefix_len = len(prefix)
+        value = value[prefix_len:]
+
+    if validators.url(value):
+        return value
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/entity_resources/sources/source/index.html b/reference/comms/entity_resources/sources/source/index.html new file mode 100644 index 000000000..089f92a25 --- /dev/null +++ b/reference/comms/entity_resources/sources/source/index.html @@ -0,0 +1,1228 @@ + + + + + + + + + + + + + + + + + + + + Source - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Source

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ BaseSource + + +

+ + +
+

+ Bases: ABC

+ + +
+ Source code in cli/medperf/comms/entity_resources/sources/source.py +
 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
class BaseSource(ABC):
+    @classmethod
+    @abstractmethod
+    def validate_resource(cls, value: str):
+        """Checks if an input resource can be downloaded by this class"""
+
+    @abstractmethod
+    def __init__(self):
+        """Initialize"""
+
+    @abstractmethod
+    def authenticate(self):
+        """Authenticates with the source server, if needed."""
+
+    @abstractmethod
+    def download(self, resource_identifier: str, output_path: str):
+        """Downloads the requested resource to the specified location
+        Args:
+            resource_identifier (str): The identifier that is used to download
+            the resource (e.g. URL, asset ID, ...) It is the parsed output
+            by `validate_resource`
+            output_path (str): The path to download the resource to
+        """
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__init__() + + + abstractmethod + + +

+ + +
+ +

Initialize

+ +
+ Source code in cli/medperf/comms/entity_resources/sources/source.py +
10
+11
+12
@abstractmethod
+def __init__(self):
+    """Initialize"""
+
+
+
+ +
+ + +
+ + + +

+authenticate() + + + abstractmethod + + +

+ + +
+ +

Authenticates with the source server, if needed.

+ +
+ Source code in cli/medperf/comms/entity_resources/sources/source.py +
14
+15
+16
@abstractmethod
+def authenticate(self):
+    """Authenticates with the source server, if needed."""
+
+
+
+ +
+ + +
+ + + +

+download(resource_identifier, output_path) + + + abstractmethod + + +

+ + +
+ +

Downloads the requested resource to the specified location

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
resource_identifier + str + +
+

The identifier that is used to download

+
+
+ required +
output_path + str + +
+

The path to download the resource to

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/entity_resources/sources/source.py +
18
+19
+20
+21
+22
+23
+24
+25
+26
@abstractmethod
+def download(self, resource_identifier: str, output_path: str):
+    """Downloads the requested resource to the specified location
+    Args:
+        resource_identifier (str): The identifier that is used to download
+        the resource (e.g. URL, asset ID, ...) It is the parsed output
+        by `validate_resource`
+        output_path (str): The path to download the resource to
+    """
+
+
+
+ +
+ + +
+ + + +

+validate_resource(value) + + + abstractmethod + classmethod + + +

+ + +
+ +

Checks if an input resource can be downloaded by this class

+ +
+ Source code in cli/medperf/comms/entity_resources/sources/source.py +
5
+6
+7
+8
@classmethod
+@abstractmethod
+def validate_resource(cls, value: str):
+    """Checks if an input resource can be downloaded by this class"""
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/entity_resources/sources/synapse/index.html b/reference/comms/entity_resources/sources/synapse/index.html new file mode 100644 index 000000000..821e08613 --- /dev/null +++ b/reference/comms/entity_resources/sources/synapse/index.html @@ -0,0 +1,1201 @@ + + + + + + + + + + + + + + + + + + + + Synapse - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Synapse

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ SynapseSource + + +

+ + +
+

+ Bases: BaseSource

+ + +
+ Source code in cli/medperf/comms/entity_resources/sources/synapse.py +
17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
class SynapseSource(BaseSource):
+    prefix = "synapse:"
+
+    @classmethod
+    def validate_resource(cls, value: str):
+        """This class expects a resource string of the form
+        `synapse:<synapse_id>`, where <synapse_id> is in the form `syn<Integer>`.
+        Args:
+            resource (str): the resource string
+
+        Returns:
+            (str|None): The synapse ID if the pattern matches, else None
+        """
+        prefix = cls.prefix
+        if not value.startswith(prefix):
+            return
+
+        prefix_len = len(prefix)
+        value = value[prefix_len:]
+
+        if re.match(r"syn\d+$", value):
+            return value
+
+    def __init__(self):
+        self.client = synapseclient.Synapse()
+
+    def authenticate(self):
+        try:
+            self.client.login(silent=True)
+        except SynapseNoCredentialsError:
+            msg = "There was an attempt to download resources from the Synapse "
+            msg += "platform, but couldn't find Synapse credentials."
+            msg += "\nDid you run 'medperf auth synapse_login' before?"
+            raise CommunicationAuthenticationError(msg)
+
+    def download(self, resource_identifier: str, output_path: str):
+        # we can specify target folder only. File name depends on how it was stored
+        download_location = os.path.dirname(output_path)
+        os.makedirs(download_location, exist_ok=True)
+        try:
+            resource_file = self.client.get(
+                resource_identifier, downloadLocation=download_location
+            )
+        except (SynapseHTTPError, SynapseUnmetAccessRestrictions) as e:
+            raise CommunicationRetrievalError(str(e))
+
+        resource_path = os.path.join(download_location, resource_file.name)
+        # synapseclient may only throw a warning in some cases
+        # (e.g. read permissions but no download permissions)
+        if not os.path.exists(resource_path):
+            raise CommunicationRetrievalError(
+                "There was a problem retrieving a file from Synapse"
+            )
+        shutil.move(resource_path, output_path)
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+validate_resource(value) + + + classmethod + + +

+ + +
+ +

This class expects a resource string of the form +synapse:<synapse_id>, where is in the form syn<Integer>.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
resource + str + +
+

the resource string

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ str | None + +
+

The synapse ID if the pattern matches, else None

+
+
+ +
+ Source code in cli/medperf/comms/entity_resources/sources/synapse.py +
20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
@classmethod
+def validate_resource(cls, value: str):
+    """This class expects a resource string of the form
+    `synapse:<synapse_id>`, where <synapse_id> is in the form `syn<Integer>`.
+    Args:
+        resource (str): the resource string
+
+    Returns:
+        (str|None): The synapse ID if the pattern matches, else None
+    """
+    prefix = cls.prefix
+    if not value.startswith(prefix):
+        return
+
+    prefix_len = len(prefix)
+    value = value[prefix_len:]
+
+    if re.match(r"syn\d+$", value):
+        return value
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/entity_resources/utils/index.html b/reference/comms/entity_resources/utils/index.html new file mode 100644 index 000000000..e3e0c0c56 --- /dev/null +++ b/reference/comms/entity_resources/utils/index.html @@ -0,0 +1,1358 @@ + + + + + + + + + + + + + + + + + + + + Utils - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Utils

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+__parse_resource(resource) + +

+ + +
+ +

Parses a resource string and returns its identifier and the source class +it can be downloaded from. +The function iterates over all supported sources and checks which one accepts +this resource. A resource is a string that should match a certain pattern to be +downloaded by a certain resource.

+

If the resource pattern does not correspond to any supported source, the +function raises an InvalidArgumentError

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
resource + str + +
+

The resource string. Must be in the form :

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/entity_resources/utils.py +
 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
def __parse_resource(resource: str):
+    """Parses a resource string and returns its identifier and the source class
+    it can be downloaded from.
+    The function iterates over all supported sources and checks which one accepts
+    this resource. A resource is a string that should match a certain pattern to be
+    downloaded by a certain resource.
+
+    If the resource pattern does not correspond to any supported source, the
+    function raises an `InvalidArgumentError`
+
+    Args:
+        resource (str): The resource string. Must be in the form <source_prefix>:<resource_identifier>
+        or a url. The later case will be interpreted as a direct download link.
+    """
+
+    for source_class in supported_sources:
+        resource_identifier = source_class.validate_resource(resource)
+        if resource_identifier:
+            return source_class, resource_identifier
+
+    # In this case the input format is not compatible with any source
+    msg = f"""Invalid resource input: {resource}. A Resource must be a url or
+    in the following format: '<source_prefix>:<resource_identifier>'. Run
+    `medperf mlcube submit --help` for more details."""
+    raise InvalidArgumentError(msg)
+
+
+
+ +
+ + +
+ + + +

+download_resource(resource, output_path, expected_hash=None) + +

+ + +
+ +

Downloads a resource/file from the internet. Passing a hash is optional. +If hash is provided, the downloaded file's hash will be checked and an error +will be raised if it is incorrect.

+

Upon success, the function returns the hash of the downloaded file.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
resource + str + +
+

The resource string. Must be in the form :

+
+
+ required +
output_path + str + +
+

The path to download the resource to

+
+
+ required +
expected_hash + (optional, str) + +
+

The expected hash of the file to be downloaded

+
+
+ None +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ +
+

The hash of the downloaded file (or existing file)

+
+
+ +
+ Source code in cli/medperf/comms/entity_resources/utils.py +
62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
def download_resource(
+    resource: str, output_path: str, expected_hash: Optional[str] = None
+):
+    """Downloads a resource/file from the internet. Passing a hash is optional.
+    If hash is provided, the downloaded file's hash will be checked and an error
+    will be raised if it is incorrect.
+
+    Upon success, the function returns the hash of the downloaded file.
+
+    Args:
+        resource (str): The resource string. Must be in the form <source_prefix>:<resource_identifier>
+        or a url.
+        output_path (str): The path to download the resource to
+        expected_hash (optional, str): The expected hash of the file to be downloaded
+
+    Returns:
+        The hash of the downloaded file (or existing file)
+
+    """
+    tmp_output_path = tmp_download_resource(resource)
+
+    calculated_hash = get_file_hash(tmp_output_path)
+
+    if expected_hash and calculated_hash != expected_hash:
+        logging.debug(f"{resource}: Expected {expected_hash}, found {calculated_hash}.")
+        raise InvalidEntityError(f"Hash mismatch: {resource}")
+
+    to_permanent_path(tmp_output_path, output_path)
+
+    return calculated_hash
+
+
+
+ +
+ + +
+ + + +

+tmp_download_resource(resource) + +

+ + +
+ +

Downloads a resource to the temporary storage.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
resource + str + +
+

The resource string. Must be in the form :

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
tmp_output_path + str + +
+

The location where the resource was downloaded

+
+
+ +
+ Source code in cli/medperf/comms/entity_resources/utils.py +
36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
def tmp_download_resource(resource):
+    """Downloads a resource to the temporary storage.
+
+    Args:
+        resource (str): The resource string. Must be in the form <source_prefix>:<resource_identifier>
+        or a url.
+
+    Returns:
+        tmp_output_path (str): The location where the resource was downloaded
+    """
+
+    tmp_output_path = generate_tmp_path()
+    source_class, resource_identifier = __parse_resource(resource)
+    source = source_class()
+    source.authenticate()
+    source.download(resource_identifier, tmp_output_path)
+    return tmp_output_path
+
+
+
+ +
+ + +
+ + + +

+to_permanent_path(tmp_output_path, output_path) + +

+ + +
+ +

Writes a file from the temporary storage to the desired output path.

+ +
+ Source code in cli/medperf/comms/entity_resources/utils.py +
55
+56
+57
+58
+59
def to_permanent_path(tmp_output_path, output_path):
+    """Writes a file from the temporary storage to the desired output path."""
+    output_folder = os.path.dirname(os.path.abspath(output_path))
+    os.makedirs(output_folder, exist_ok=True)
+    os.rename(tmp_output_path, output_path)
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/factory/index.html b/reference/comms/factory/index.html new file mode 100644 index 000000000..9f5a17938 --- /dev/null +++ b/reference/comms/factory/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Factory - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Factory

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/interface/index.html b/reference/comms/interface/index.html new file mode 100644 index 000000000..dbf8dc47a --- /dev/null +++ b/reference/comms/interface/index.html @@ -0,0 +1,4227 @@ + + + + + + + + + + + + + + + + + + + + Interface - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Interface

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Comms + + +

+ + +
+

+ Bases: ABC

+ + +
+ Source code in cli/medperf/comms/interface.py +
  5
+  6
+  7
+  8
+  9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
class Comms(ABC):
+    @abstractmethod
+    def __init__(self, source: str):
+        """Create an instance of a communication object.
+
+        Args:
+            source (str): location of the communication source. Where messages are going to be sent.
+            ui (UI): Implementation of the UI interface.
+            token (str, Optional): authentication token to be used throughout communication. Defaults to None.
+        """
+
+    @classmethod
+    @abstractmethod
+    def parse_url(self, url: str) -> str:
+        """Parse the source URL so that it can be used by the comms implementation.
+        It should handle protocols and versioning to be able to communicate with the API.
+
+        Args:
+            url (str): base URL
+
+        Returns:
+            str: parsed URL with protocol and version
+        """
+
+    @abstractmethod
+    def get_current_user(self):
+        """Retrieve the currently-authenticated user information"""
+
+    @abstractmethod
+    def get_benchmarks(self) -> List[dict]:
+        """Retrieves all benchmarks in the platform.
+
+        Returns:
+            List[dict]: all benchmarks information.
+        """
+
+    @abstractmethod
+    def get_benchmark(self, benchmark_uid: int) -> dict:
+        """Retrieves the benchmark specification file from the server
+
+        Args:
+            benchmark_uid (int): uid for the desired benchmark
+
+        Returns:
+            dict: benchmark specification
+        """
+
+    @abstractmethod
+    def get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:
+        """Retrieves all the model associations of a benchmark.
+
+        Args:
+            benchmark_uid (int): UID of the desired benchmark
+
+        Returns:
+            list[int]: List of benchmark model associations
+        """
+
+    @abstractmethod
+    def get_user_benchmarks(self) -> List[dict]:
+        """Retrieves all benchmarks created by the user
+
+        Returns:
+            List[dict]: Benchmarks data
+        """
+
+    @abstractmethod
+    def get_cubes(self) -> List[dict]:
+        """Retrieves all MLCubes in the platform
+
+        Returns:
+            List[dict]: List containing the data of all MLCubes
+        """
+
+    @abstractmethod
+    def get_cube_metadata(self, cube_uid: int) -> dict:
+        """Retrieves metadata about the specified cube
+
+        Args:
+            cube_uid (int): UID of the desired cube.
+
+        Returns:
+            dict: Dictionary containing url and hashes for the cube files
+        """
+
+    @abstractmethod
+    def get_user_cubes(self) -> List[dict]:
+        """Retrieves metadata from all cubes registered by the user
+
+        Returns:
+            List[dict]: List of dictionaries containing the mlcubes registration information
+        """
+
+    @abstractmethod
+    def upload_benchmark(self, benchmark_dict: dict) -> int:
+        """Uploads a new benchmark to the server.
+
+        Args:
+            benchmark_dict (dict): benchmark_data to be uploaded
+
+        Returns:
+            int: UID of newly created benchmark
+        """
+
+    @abstractmethod
+    def upload_mlcube(self, mlcube_body: dict) -> int:
+        """Uploads an MLCube instance to the platform
+
+        Args:
+            mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes
+
+        Returns:
+            int: id of the created mlcube instance on the platform
+        """
+
+    @abstractmethod
+    def get_datasets(self) -> List[dict]:
+        """Retrieves all datasets in the platform
+
+        Returns:
+            List[dict]: List of data from all datasets
+        """
+
+    @abstractmethod
+    def get_dataset(self, dset_uid: str) -> dict:
+        """Retrieves a specific dataset
+
+        Args:
+            dset_uid (str): Dataset UID
+
+        Returns:
+            dict: Dataset metadata
+        """
+
+    @abstractmethod
+    def get_user_datasets(self) -> dict:
+        """Retrieves all datasets registered by the user
+
+        Returns:
+            dict: dictionary with the contents of each dataset registration query
+        """
+
+    @abstractmethod
+    def upload_dataset(self, reg_dict: dict) -> int:
+        """Uploads registration data to the server, under the sha name of the file.
+
+        Args:
+            reg_dict (dict): Dictionary containing registration information.
+
+        Returns:
+            int: id of the created dataset registration.
+        """
+
+    @abstractmethod
+    def get_results(self) -> List[dict]:
+        """Retrieves all results
+
+        Returns:
+            List[dict]: List of results
+        """
+
+    @abstractmethod
+    def get_result(self, result_uid: str) -> dict:
+        """Retrieves a specific result data
+
+        Args:
+            result_uid (str): Result UID
+
+        Returns:
+            dict: Result metadata
+        """
+
+    @abstractmethod
+    def get_user_results(self) -> dict:
+        """Retrieves all results registered by the user
+
+        Returns:
+            dict: dictionary with the contents of each dataset registration query
+        """
+
+    @abstractmethod
+    def get_benchmark_results(self, benchmark_id: int) -> dict:
+        """Retrieves all results for a given benchmark
+
+        Args:
+            benchmark_id (int): benchmark ID to retrieve results from
+
+        Returns:
+            dict: dictionary with the contents of each result in the specified benchmark
+        """
+
+    @abstractmethod
+    def upload_result(self, results_dict: dict) -> int:
+        """Uploads result to the server.
+
+        Args:
+            results_dict (dict): Dictionary containing results information.
+
+        Returns:
+            int: id of the generated results entry
+        """
+
+    @abstractmethod
+    def associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):
+        """Create a Dataset Benchmark association
+
+        Args:
+            data_uid (int): Registered dataset UID
+            benchmark_uid (int): Benchmark UID
+            metadata (dict, optional): Additional metadata. Defaults to {}.
+        """
+
+    @abstractmethod
+    def associate_cube(self, cube_uid: str, benchmark_uid: int, metadata: dict = {}):
+        """Create an MLCube-Benchmark association
+
+        Args:
+            cube_uid (str): MLCube UID
+            benchmark_uid (int): Benchmark UID
+            metadata (dict, optional): Additional metadata. Defaults to {}.
+        """
+
+    @abstractmethod
+    def set_dataset_association_approval(
+        self, dataset_uid: str, benchmark_uid: str, status: str
+    ):
+        """Approves a dataset association
+
+        Args:
+            dataset_uid (str): Dataset UID
+            benchmark_uid (str): Benchmark UID
+            status (str): Approval status to set for the association
+        """
+
+    @abstractmethod
+    def set_mlcube_association_approval(
+        self, mlcube_uid: str, benchmark_uid: str, status: str
+    ):
+        """Approves an mlcube association
+
+        Args:
+            mlcube_uid (str): Dataset UID
+            benchmark_uid (str): Benchmark UID
+            status (str): Approval status to set for the association
+        """
+
+    @abstractmethod
+    def get_datasets_associations(self) -> List[dict]:
+        """Get all dataset associations related to the current user
+
+        Returns:
+            List[dict]: List containing all associations information
+        """
+
+    @abstractmethod
+    def get_cubes_associations(self) -> List[dict]:
+        """Get all cube associations related to the current user
+
+        Returns:
+            List[dict]: List containing all associations information
+        """
+
+    @abstractmethod
+    def set_mlcube_association_priority(
+        self, benchmark_uid: str, mlcube_uid: str, priority: int
+    ):
+        """Sets the priority of an mlcube-benchmark association
+
+        Args:
+            mlcube_uid (str): MLCube UID
+            benchmark_uid (str): Benchmark UID
+            priority (int): priority value to set for the association
+        """
+
+    @abstractmethod
+    def update_dataset(self, dataset_id: int, data: dict):
+        """Updates the contents of a datasets identified by dataset_id to the new data dictionary.
+        Updates may be partial.
+
+        Args:
+            dataset_id (int): ID of the dataset to update
+            data (dict): Updated information of the dataset.
+        """
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__init__(source) + + + abstractmethod + + +

+ + +
+ +

Create an instance of a communication object.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
source + str + +
+

location of the communication source. Where messages are going to be sent.

+
+
+ required +
ui + UI + +
+

Implementation of the UI interface.

+
+
+ required +
token + (str, Optional) + +
+

authentication token to be used throughout communication. Defaults to None.

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/interface.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
@abstractmethod
+def __init__(self, source: str):
+    """Create an instance of a communication object.
+
+    Args:
+        source (str): location of the communication source. Where messages are going to be sent.
+        ui (UI): Implementation of the UI interface.
+        token (str, Optional): authentication token to be used throughout communication. Defaults to None.
+    """
+
+
+
+ +
+ + +
+ + + +

+associate_cube(cube_uid, benchmark_uid, metadata={}) + + + abstractmethod + + +

+ + +
+ +

Create an MLCube-Benchmark association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
cube_uid + str + +
+

MLCube UID

+
+
+ required +
benchmark_uid + int + +
+

Benchmark UID

+
+
+ required +
metadata + dict + +
+

Additional metadata. Defaults to {}.

+
+
+ {} +
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def associate_cube(self, cube_uid: str, benchmark_uid: int, metadata: dict = {}):
+    """Create an MLCube-Benchmark association
+
+    Args:
+        cube_uid (str): MLCube UID
+        benchmark_uid (int): Benchmark UID
+        metadata (dict, optional): Additional metadata. Defaults to {}.
+    """
+
+
+
+ +
+ + +
+ + + +

+associate_dset(data_uid, benchmark_uid, metadata={}) + + + abstractmethod + + +

+ + +
+ +

Create a Dataset Benchmark association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
data_uid + int + +
+

Registered dataset UID

+
+
+ required +
benchmark_uid + int + +
+

Benchmark UID

+
+
+ required +
metadata + dict + +
+

Additional metadata. Defaults to {}.

+
+
+ {} +
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):
+    """Create a Dataset Benchmark association
+
+    Args:
+        data_uid (int): Registered dataset UID
+        benchmark_uid (int): Benchmark UID
+        metadata (dict, optional): Additional metadata. Defaults to {}.
+    """
+
+
+
+ +
+ + +
+ + + +

+get_benchmark(benchmark_uid) + + + abstractmethod + + +

+ + +
+ +

Retrieves the benchmark specification file from the server

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

uid for the desired benchmark

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

benchmark specification

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
41
+42
+43
+44
+45
+46
+47
+48
+49
+50
@abstractmethod
+def get_benchmark(self, benchmark_uid: int) -> dict:
+    """Retrieves the benchmark specification file from the server
+
+    Args:
+        benchmark_uid (int): uid for the desired benchmark
+
+    Returns:
+        dict: benchmark specification
+    """
+
+
+
+ +
+ + +
+ + + +

+get_benchmark_model_associations(benchmark_uid) + + + abstractmethod + + +

+ + +
+ +

Retrieves all the model associations of a benchmark.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

UID of the desired benchmark

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[int] + +
+

list[int]: List of benchmark model associations

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
52
+53
+54
+55
+56
+57
+58
+59
+60
+61
@abstractmethod
+def get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:
+    """Retrieves all the model associations of a benchmark.
+
+    Args:
+        benchmark_uid (int): UID of the desired benchmark
+
+    Returns:
+        list[int]: List of benchmark model associations
+    """
+
+
+
+ +
+ + +
+ + + +

+get_benchmark_results(benchmark_id) + + + abstractmethod + + +

+ + +
+ +

Retrieves all results for a given benchmark

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_id + int + +
+

benchmark ID to retrieve results from

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary with the contents of each result in the specified benchmark

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_benchmark_results(self, benchmark_id: int) -> dict:
+    """Retrieves all results for a given benchmark
+
+    Args:
+        benchmark_id (int): benchmark ID to retrieve results from
+
+    Returns:
+        dict: dictionary with the contents of each result in the specified benchmark
+    """
+
+
+
+ +
+ + +
+ + + +

+get_benchmarks() + + + abstractmethod + + +

+ + +
+ +

Retrieves all benchmarks in the platform.

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: all benchmarks information.

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
33
+34
+35
+36
+37
+38
+39
@abstractmethod
+def get_benchmarks(self) -> List[dict]:
+    """Retrieves all benchmarks in the platform.
+
+    Returns:
+        List[dict]: all benchmarks information.
+    """
+
+
+
+ +
+ + +
+ + + +

+get_cube_metadata(cube_uid) + + + abstractmethod + + +

+ + +
+ +

Retrieves metadata about the specified cube

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
cube_uid + int + +
+

UID of the desired cube.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

Dictionary containing url and hashes for the cube files

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
79
+80
+81
+82
+83
+84
+85
+86
+87
+88
@abstractmethod
+def get_cube_metadata(self, cube_uid: int) -> dict:
+    """Retrieves metadata about the specified cube
+
+    Args:
+        cube_uid (int): UID of the desired cube.
+
+    Returns:
+        dict: Dictionary containing url and hashes for the cube files
+    """
+
+
+
+ +
+ + +
+ + + +

+get_cubes() + + + abstractmethod + + +

+ + +
+ +

Retrieves all MLCubes in the platform

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List containing the data of all MLCubes

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
71
+72
+73
+74
+75
+76
+77
@abstractmethod
+def get_cubes(self) -> List[dict]:
+    """Retrieves all MLCubes in the platform
+
+    Returns:
+        List[dict]: List containing the data of all MLCubes
+    """
+
+
+
+ +
+ + +
+ + + +

+get_cubes_associations() + + + abstractmethod + + +

+ + +
+ +

Get all cube associations related to the current user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List containing all associations information

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_cubes_associations(self) -> List[dict]:
+    """Get all cube associations related to the current user
+
+    Returns:
+        List[dict]: List containing all associations information
+    """
+
+
+
+ +
+ + +
+ + + +

+get_current_user() + + + abstractmethod + + +

+ + +
+ +

Retrieve the currently-authenticated user information

+ +
+ Source code in cli/medperf/comms/interface.py +
29
+30
+31
@abstractmethod
+def get_current_user(self):
+    """Retrieve the currently-authenticated user information"""
+
+
+
+ +
+ + +
+ + + +

+get_dataset(dset_uid) + + + abstractmethod + + +

+ + +
+ +

Retrieves a specific dataset

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dset_uid + str + +
+

Dataset UID

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

Dataset metadata

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_dataset(self, dset_uid: str) -> dict:
+    """Retrieves a specific dataset
+
+    Args:
+        dset_uid (str): Dataset UID
+
+    Returns:
+        dict: Dataset metadata
+    """
+
+
+
+ +
+ + +
+ + + +

+get_datasets() + + + abstractmethod + + +

+ + +
+ +

Retrieves all datasets in the platform

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List of data from all datasets

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_datasets(self) -> List[dict]:
+    """Retrieves all datasets in the platform
+
+    Returns:
+        List[dict]: List of data from all datasets
+    """
+
+
+
+ +
+ + +
+ + + +

+get_datasets_associations() + + + abstractmethod + + +

+ + +
+ +

Get all dataset associations related to the current user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List containing all associations information

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_datasets_associations(self) -> List[dict]:
+    """Get all dataset associations related to the current user
+
+    Returns:
+        List[dict]: List containing all associations information
+    """
+
+
+
+ +
+ + +
+ + + +

+get_result(result_uid) + + + abstractmethod + + +

+ + +
+ +

Retrieves a specific result data

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
result_uid + str + +
+

Result UID

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

Result metadata

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_result(self, result_uid: str) -> dict:
+    """Retrieves a specific result data
+
+    Args:
+        result_uid (str): Result UID
+
+    Returns:
+        dict: Result metadata
+    """
+
+
+
+ +
+ + +
+ + + +

+get_results() + + + abstractmethod + + +

+ + +
+ +

Retrieves all results

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List of results

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_results(self) -> List[dict]:
+    """Retrieves all results
+
+    Returns:
+        List[dict]: List of results
+    """
+
+
+
+ +
+ + +
+ + + +

+get_user_benchmarks() + + + abstractmethod + + +

+ + +
+ +

Retrieves all benchmarks created by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: Benchmarks data

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
63
+64
+65
+66
+67
+68
+69
@abstractmethod
+def get_user_benchmarks(self) -> List[dict]:
+    """Retrieves all benchmarks created by the user
+
+    Returns:
+        List[dict]: Benchmarks data
+    """
+
+
+
+ +
+ + +
+ + + +

+get_user_cubes() + + + abstractmethod + + +

+ + +
+ +

Retrieves metadata from all cubes registered by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List of dictionaries containing the mlcubes registration information

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
90
+91
+92
+93
+94
+95
+96
@abstractmethod
+def get_user_cubes(self) -> List[dict]:
+    """Retrieves metadata from all cubes registered by the user
+
+    Returns:
+        List[dict]: List of dictionaries containing the mlcubes registration information
+    """
+
+
+
+ +
+ + +
+ + + +

+get_user_datasets() + + + abstractmethod + + +

+ + +
+ +

Retrieves all datasets registered by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary with the contents of each dataset registration query

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_user_datasets(self) -> dict:
+    """Retrieves all datasets registered by the user
+
+    Returns:
+        dict: dictionary with the contents of each dataset registration query
+    """
+
+
+
+ +
+ + +
+ + + +

+get_user_results() + + + abstractmethod + + +

+ + +
+ +

Retrieves all results registered by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary with the contents of each dataset registration query

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def get_user_results(self) -> dict:
+    """Retrieves all results registered by the user
+
+    Returns:
+        dict: dictionary with the contents of each dataset registration query
+    """
+
+
+
+ +
+ + +
+ + + +

+parse_url(url) + + + abstractmethod + classmethod + + +

+ + +
+ +

Parse the source URL so that it can be used by the comms implementation. +It should handle protocols and versioning to be able to communicate with the API.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
url + str + +
+

base URL

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

parsed URL with protocol and version

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
@classmethod
+@abstractmethod
+def parse_url(self, url: str) -> str:
+    """Parse the source URL so that it can be used by the comms implementation.
+    It should handle protocols and versioning to be able to communicate with the API.
+
+    Args:
+        url (str): base URL
+
+    Returns:
+        str: parsed URL with protocol and version
+    """
+
+
+
+ +
+ + +
+ + + +

+set_dataset_association_approval(dataset_uid, benchmark_uid, status) + + + abstractmethod + + +

+ + +
+ +

Approves a dataset association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dataset_uid + str + +
+

Dataset UID

+
+
+ required +
benchmark_uid + str + +
+

Benchmark UID

+
+
+ required +
status + str + +
+

Approval status to set for the association

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def set_dataset_association_approval(
+    self, dataset_uid: str, benchmark_uid: str, status: str
+):
+    """Approves a dataset association
+
+    Args:
+        dataset_uid (str): Dataset UID
+        benchmark_uid (str): Benchmark UID
+        status (str): Approval status to set for the association
+    """
+
+
+
+ +
+ + +
+ + + +

+set_mlcube_association_approval(mlcube_uid, benchmark_uid, status) + + + abstractmethod + + +

+ + +
+ +

Approves an mlcube association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
mlcube_uid + str + +
+

Dataset UID

+
+
+ required +
benchmark_uid + str + +
+

Benchmark UID

+
+
+ required +
status + str + +
+

Approval status to set for the association

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def set_mlcube_association_approval(
+    self, mlcube_uid: str, benchmark_uid: str, status: str
+):
+    """Approves an mlcube association
+
+    Args:
+        mlcube_uid (str): Dataset UID
+        benchmark_uid (str): Benchmark UID
+        status (str): Approval status to set for the association
+    """
+
+
+
+ +
+ + +
+ + + +

+set_mlcube_association_priority(benchmark_uid, mlcube_uid, priority) + + + abstractmethod + + +

+ + +
+ +

Sets the priority of an mlcube-benchmark association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
mlcube_uid + str + +
+

MLCube UID

+
+
+ required +
benchmark_uid + str + +
+

Benchmark UID

+
+
+ required +
priority + int + +
+

priority value to set for the association

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def set_mlcube_association_priority(
+    self, benchmark_uid: str, mlcube_uid: str, priority: int
+):
+    """Sets the priority of an mlcube-benchmark association
+
+    Args:
+        mlcube_uid (str): MLCube UID
+        benchmark_uid (str): Benchmark UID
+        priority (int): priority value to set for the association
+    """
+
+
+
+ +
+ + +
+ + + +

+update_dataset(dataset_id, data) + + + abstractmethod + + +

+ + +
+ +

Updates the contents of a datasets identified by dataset_id to the new data dictionary. +Updates may be partial.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dataset_id + int + +
+

ID of the dataset to update

+
+
+ required +
data + dict + +
+

Updated information of the dataset.

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def update_dataset(self, dataset_id: int, data: dict):
+    """Updates the contents of a datasets identified by dataset_id to the new data dictionary.
+    Updates may be partial.
+
+    Args:
+        dataset_id (int): ID of the dataset to update
+        data (dict): Updated information of the dataset.
+    """
+
+
+
+ +
+ + +
+ + + +

+upload_benchmark(benchmark_dict) + + + abstractmethod + + +

+ + +
+ +

Uploads a new benchmark to the server.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_dict + dict + +
+

benchmark_data to be uploaded

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

UID of newly created benchmark

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def upload_benchmark(self, benchmark_dict: dict) -> int:
+    """Uploads a new benchmark to the server.
+
+    Args:
+        benchmark_dict (dict): benchmark_data to be uploaded
+
+    Returns:
+        int: UID of newly created benchmark
+    """
+
+
+
+ +
+ + +
+ + + +

+upload_dataset(reg_dict) + + + abstractmethod + + +

+ + +
+ +

Uploads registration data to the server, under the sha name of the file.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
reg_dict + dict + +
+

Dictionary containing registration information.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

id of the created dataset registration.

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def upload_dataset(self, reg_dict: dict) -> int:
+    """Uploads registration data to the server, under the sha name of the file.
+
+    Args:
+        reg_dict (dict): Dictionary containing registration information.
+
+    Returns:
+        int: id of the created dataset registration.
+    """
+
+
+
+ +
+ + +
+ + + +

+upload_mlcube(mlcube_body) + + + abstractmethod + + +

+ + +
+ +

Uploads an MLCube instance to the platform

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
mlcube_body + dict + +
+

Dictionary containing all the relevant data for creating mlcubes

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

id of the created mlcube instance on the platform

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def upload_mlcube(self, mlcube_body: dict) -> int:
+    """Uploads an MLCube instance to the platform
+
+    Args:
+        mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes
+
+    Returns:
+        int: id of the created mlcube instance on the platform
+    """
+
+
+
+ +
+ + +
+ + + +

+upload_result(results_dict) + + + abstractmethod + + +

+ + +
+ +

Uploads result to the server.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
results_dict + dict + +
+

Dictionary containing results information.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

id of the generated results entry

+
+
+ +
+ Source code in cli/medperf/comms/interface.py +
@abstractmethod
+def upload_result(self, results_dict: dict) -> int:
+    """Uploads result to the server.
+
+    Args:
+        results_dict (dict): Dictionary containing results information.
+
+    Returns:
+        int: id of the generated results entry
+    """
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/comms/rest/index.html b/reference/comms/rest/index.html new file mode 100644 index 000000000..35bdd9755 --- /dev/null +++ b/reference/comms/rest/index.html @@ -0,0 +1,5103 @@ + + + + + + + + + + + + + + + + + + + + Rest - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Rest

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ REST + + +

+ + +
+

+ Bases: Comms

+ + +
+ Source code in cli/medperf/comms/rest.py +
 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
class REST(Comms):
+    def __init__(self, source: str):
+        self.server_url = self.parse_url(source)
+        self.cert = config.certificate
+        if self.cert is None:
+            # No certificate provided, default to normal verification
+            self.cert = True
+
+    @classmethod
+    def parse_url(cls, url: str) -> str:
+        """Parse the source URL so that it can be used by the comms implementation.
+        It should handle protocols and versioning to be able to communicate with the API.
+
+        Args:
+            url (str): base URL
+
+        Returns:
+            str: parsed URL with protocol and version
+        """
+        url_sections = url.split("://")
+        api_path = f"/api/v{config.major_version}"
+        # Remove protocol if passed
+        if len(url_sections) > 1:
+            url = "".join(url_sections[1:])
+
+        return f"https://{url}{api_path}"
+
+    def __auth_get(self, url, **kwargs):
+        return self.__auth_req(url, requests.get, **kwargs)
+
+    def __auth_post(self, url, **kwargs):
+        return self.__auth_req(url, requests.post, **kwargs)
+
+    def __auth_put(self, url, **kwargs):
+        return self.__auth_req(url, requests.put, **kwargs)
+
+    def __auth_req(self, url, req_func, **kwargs):
+        token = config.auth.access_token
+        return self.__req(
+            url, req_func, headers={"Authorization": f"Bearer {token}"}, **kwargs
+        )
+
+    def __req(self, url, req_func, **kwargs):
+        logging.debug(f"Calling {req_func}: {url}")
+        if "json" in kwargs:
+            logging.debug(f"Passing JSON contents: {kwargs['json']}")
+            kwargs["json"] = sanitize_json(kwargs["json"])
+        try:
+            return req_func(url, verify=self.cert, **kwargs)
+        except requests.exceptions.SSLError as e:
+            logging.error(f"Couldn't connect to {self.server_url}: {e}")
+            raise CommunicationError(
+                "Couldn't connect to server through HTTPS. If running locally, "
+                "remember to provide the server certificate through --certificate"
+            )
+
+    def __get_list(
+        self,
+        url,
+        num_elements=None,
+        page_size=config.default_page_size,
+        offset=0,
+        binary_reduction=False,
+    ):
+        """Retrieves a list of elements from a URL by iterating over pages until num_elements is obtained.
+        If num_elements is None, then iterates until all elements have been retrieved.
+        If binary_reduction is enabled, errors are assumed to be related to response size. In that case,
+        the page_size is reduced by half until a successful response is obtained or until page_size can't be
+        reduced anymore.
+
+        Args:
+            url (str): The url to retrieve elements from
+            num_elements (int, optional): The desired number of elements to be retrieved. Defaults to None.
+            page_size (int, optional): Starting page size. Defaults to config.default_page_size.
+            start_limit (int, optional): The starting position for element retrieval. Defaults to 0.
+            binary_reduction (bool, optional): Wether to handle errors by halfing the page size. Defaults to False.
+
+        Returns:
+            List[dict]: A list of dictionaries representing the retrieved elements.
+        """
+        el_list = []
+
+        if num_elements is None:
+            num_elements = float("inf")
+
+        while len(el_list) < num_elements:
+            paginated_url = f"{url}?limit={page_size}&offset={offset}"
+            res = self.__auth_get(paginated_url)
+            if res.status_code != 200:
+                if not binary_reduction:
+                    log_response_error(res)
+                    details = format_errors_dict(res.json())
+                    raise CommunicationRetrievalError(
+                        f"there was an error retrieving the current list: {details}"
+                    )
+
+                log_response_error(res, warn=True)
+                details = format_errors_dict(res.json())
+                if page_size <= 1:
+                    raise CommunicationRetrievalError(
+                        f"Could not retrieve list. Minimum page size achieved without success: {details}"
+                    )
+                page_size = page_size // 2
+                continue
+            else:
+                data = res.json()
+                el_list += data["results"]
+                offset += len(data["results"])
+                if data["next"] is None:
+                    break
+
+        if isinstance(num_elements, int):
+            return el_list[:num_elements]
+        return el_list
+
+    def __set_approval_status(self, url: str, status: str) -> requests.Response:
+        """Sets the approval status of a resource
+
+        Args:
+            url (str): URL to the resource to update
+            status (str): approval status to set
+
+        Returns:
+            requests.Response: Response object returned by the update
+        """
+        data = {"approval_status": status}
+        res = self.__auth_put(url, json=data)
+        return res
+
+    def get_current_user(self):
+        """Retrieve the currently-authenticated user information"""
+        res = self.__auth_get(f"{self.server_url}/me/")
+        return res.json()
+
+    def get_benchmarks(self) -> List[dict]:
+        """Retrieves all benchmarks in the platform.
+
+        Returns:
+            List[dict]: all benchmarks information.
+        """
+        bmks = self.__get_list(f"{self.server_url}/benchmarks/")
+        return bmks
+
+    def get_benchmark(self, benchmark_uid: int) -> dict:
+        """Retrieves the benchmark specification file from the server
+
+        Args:
+            benchmark_uid (int): uid for the desired benchmark
+
+        Returns:
+            dict: benchmark specification
+        """
+        res = self.__auth_get(f"{self.server_url}/benchmarks/{benchmark_uid}")
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRetrievalError(
+                f"the specified benchmark doesn't exist: {details}"
+            )
+        return res.json()
+
+    def get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:
+        """Retrieves all the model associations of a benchmark.
+
+        Args:
+            benchmark_uid (int): UID of the desired benchmark
+
+        Returns:
+            list[int]: List of benchmark model associations
+        """
+        assocs = self.__get_list(f"{self.server_url}/benchmarks/{benchmark_uid}/models")
+        return filter_latest_associations(assocs, "model_mlcube")
+
+    def get_user_benchmarks(self) -> List[dict]:
+        """Retrieves all benchmarks created by the user
+
+        Returns:
+            List[dict]: Benchmarks data
+        """
+        bmks = self.__get_list(f"{self.server_url}/me/benchmarks/")
+        return bmks
+
+    def get_cubes(self) -> List[dict]:
+        """Retrieves all MLCubes in the platform
+
+        Returns:
+            List[dict]: List containing the data of all MLCubes
+        """
+        cubes = self.__get_list(f"{self.server_url}/mlcubes/")
+        return cubes
+
+    def get_cube_metadata(self, cube_uid: int) -> dict:
+        """Retrieves metadata about the specified cube
+
+        Args:
+            cube_uid (int): UID of the desired cube.
+
+        Returns:
+            dict: Dictionary containing url and hashes for the cube files
+        """
+        res = self.__auth_get(f"{self.server_url}/mlcubes/{cube_uid}/")
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRetrievalError(
+                f"the specified cube doesn't exist {details}"
+            )
+        return res.json()
+
+    def get_user_cubes(self) -> List[dict]:
+        """Retrieves metadata from all cubes registered by the user
+
+        Returns:
+            List[dict]: List of dictionaries containing the mlcubes registration information
+        """
+        cubes = self.__get_list(f"{self.server_url}/me/mlcubes/")
+        return cubes
+
+    def upload_benchmark(self, benchmark_dict: dict) -> int:
+        """Uploads a new benchmark to the server.
+
+        Args:
+            benchmark_dict (dict): benchmark_data to be uploaded
+
+        Returns:
+            int: UID of newly created benchmark
+        """
+        res = self.__auth_post(f"{self.server_url}/benchmarks/", json=benchmark_dict)
+        if res.status_code != 201:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRetrievalError(f"Could not upload benchmark: {details}")
+        return res.json()
+
+    def upload_mlcube(self, mlcube_body: dict) -> int:
+        """Uploads an MLCube instance to the platform
+
+        Args:
+            mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes
+
+        Returns:
+            int: id of the created mlcube instance on the platform
+        """
+        res = self.__auth_post(f"{self.server_url}/mlcubes/", json=mlcube_body)
+        if res.status_code != 201:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRetrievalError(f"Could not upload the mlcube: {details}")
+        return res.json()
+
+    def get_datasets(self) -> List[dict]:
+        """Retrieves all datasets in the platform
+
+        Returns:
+            List[dict]: List of data from all datasets
+        """
+        dsets = self.__get_list(f"{self.server_url}/datasets/")
+        return dsets
+
+    def get_dataset(self, dset_uid: int) -> dict:
+        """Retrieves a specific dataset
+
+        Args:
+            dset_uid (int): Dataset UID
+
+        Returns:
+            dict: Dataset metadata
+        """
+        res = self.__auth_get(f"{self.server_url}/datasets/{dset_uid}/")
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRetrievalError(
+                f"Could not retrieve the specified dataset from server: {details}"
+            )
+        return res.json()
+
+    def get_user_datasets(self) -> dict:
+        """Retrieves all datasets registered by the user
+
+        Returns:
+            dict: dictionary with the contents of each dataset registration query
+        """
+        dsets = self.__get_list(f"{self.server_url}/me/datasets/")
+        return dsets
+
+    def upload_dataset(self, reg_dict: dict) -> int:
+        """Uploads registration data to the server, under the sha name of the file.
+
+        Args:
+            reg_dict (dict): Dictionary containing registration information.
+
+        Returns:
+            int: id of the created dataset registration.
+        """
+        res = self.__auth_post(f"{self.server_url}/datasets/", json=reg_dict)
+        if res.status_code != 201:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(f"Could not upload the dataset: {details}")
+        return res.json()
+
+    def get_results(self) -> List[dict]:
+        """Retrieves all results
+
+        Returns:
+            List[dict]: List of results
+        """
+        res = self.__get_list(f"{self.server_url}/results")
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRetrievalError(f"Could not retrieve results: {details}")
+        return res.json()
+
+    def get_result(self, result_uid: int) -> dict:
+        """Retrieves a specific result data
+
+        Args:
+            result_uid (int): Result UID
+
+        Returns:
+            dict: Result metadata
+        """
+        res = self.__auth_get(f"{self.server_url}/results/{result_uid}/")
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRetrievalError(
+                f"Could not retrieve the specified result: {details}"
+            )
+        return res.json()
+
+    def get_user_results(self) -> dict:
+        """Retrieves all results registered by the user
+
+        Returns:
+            dict: dictionary with the contents of each result registration query
+        """
+        results = self.__get_list(f"{self.server_url}/me/results/")
+        return results
+
+    def get_benchmark_results(self, benchmark_id: int) -> dict:
+        """Retrieves all results for a given benchmark
+
+        Args:
+            benchmark_id (int): benchmark ID to retrieve results from
+
+        Returns:
+            dict: dictionary with the contents of each result in the specified benchmark
+        """
+        results = self.__get_list(
+            f"{self.server_url}/benchmarks/{benchmark_id}/results"
+        )
+        return results
+
+    def upload_result(self, results_dict: dict) -> int:
+        """Uploads result to the server.
+
+        Args:
+            results_dict (dict): Dictionary containing results information.
+
+        Returns:
+            int: id of the generated results entry
+        """
+        res = self.__auth_post(f"{self.server_url}/results/", json=results_dict)
+        if res.status_code != 201:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(f"Could not upload the results: {details}")
+        return res.json()
+
+    def associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):
+        """Create a Dataset Benchmark association
+
+        Args:
+            data_uid (int): Registered dataset UID
+            benchmark_uid (int): Benchmark UID
+            metadata (dict, optional): Additional metadata. Defaults to {}.
+        """
+        data = {
+            "dataset": data_uid,
+            "benchmark": benchmark_uid,
+            "approval_status": Status.PENDING.value,
+            "metadata": metadata,
+        }
+        res = self.__auth_post(f"{self.server_url}/datasets/benchmarks/", json=data)
+        if res.status_code != 201:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(
+                f"Could not associate dataset to benchmark: {details}"
+            )
+
+    def associate_cube(self, cube_uid: int, benchmark_uid: int, metadata: dict = {}):
+        """Create an MLCube-Benchmark association
+
+        Args:
+            cube_uid (int): MLCube UID
+            benchmark_uid (int): Benchmark UID
+            metadata (dict, optional): Additional metadata. Defaults to {}.
+        """
+        data = {
+            "approval_status": Status.PENDING.value,
+            "model_mlcube": cube_uid,
+            "benchmark": benchmark_uid,
+            "metadata": metadata,
+        }
+        res = self.__auth_post(f"{self.server_url}/mlcubes/benchmarks/", json=data)
+        if res.status_code != 201:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(
+                f"Could not associate mlcube to benchmark: {details}"
+            )
+
+    def set_dataset_association_approval(
+        self, benchmark_uid: int, dataset_uid: int, status: str
+    ):
+        """Approves a dataset association
+
+        Args:
+            dataset_uid (int): Dataset UID
+            benchmark_uid (int): Benchmark UID
+            status (str): Approval status to set for the association
+        """
+        url = f"{self.server_url}/datasets/{dataset_uid}/benchmarks/{benchmark_uid}/"
+        res = self.__set_approval_status(url, status)
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(
+                f"Could not approve association between dataset {dataset_uid} and benchmark {benchmark_uid}: {details}"
+            )
+
+    def set_mlcube_association_approval(
+        self, benchmark_uid: int, mlcube_uid: int, status: str
+    ):
+        """Approves an mlcube association
+
+        Args:
+            mlcube_uid (int): Dataset UID
+            benchmark_uid (int): Benchmark UID
+            status (str): Approval status to set for the association
+        """
+        url = f"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/"
+        res = self.__set_approval_status(url, status)
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(
+                f"Could not approve association between mlcube {mlcube_uid} and benchmark {benchmark_uid}: {details}"
+            )
+
+    def get_datasets_associations(self) -> List[dict]:
+        """Get all dataset associations related to the current user
+
+        Returns:
+            List[dict]: List containing all associations information
+        """
+        assocs = self.__get_list(f"{self.server_url}/me/datasets/associations/")
+        return filter_latest_associations(assocs, "dataset")
+
+    def get_cubes_associations(self) -> List[dict]:
+        """Get all cube associations related to the current user
+
+        Returns:
+            List[dict]: List containing all associations information
+        """
+        assocs = self.__get_list(f"{self.server_url}/me/mlcubes/associations/")
+        return filter_latest_associations(assocs, "model_mlcube")
+
+    def set_mlcube_association_priority(
+        self, benchmark_uid: int, mlcube_uid: int, priority: int
+    ):
+        """Sets the priority of an mlcube-benchmark association
+
+        Args:
+            mlcube_uid (int): MLCube UID
+            benchmark_uid (int): Benchmark UID
+            priority (int): priority value to set for the association
+        """
+        url = f"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/"
+        data = {"priority": priority}
+        res = self.__auth_put(url, json=data)
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(
+                f"Could not set the priority of mlcube {mlcube_uid} within the benchmark {benchmark_uid}: {details}"
+            )
+
+    def update_dataset(self, dataset_id: int, data: dict):
+        url = f"{self.server_url}/datasets/{dataset_id}/"
+        res = self.__auth_put(url, json=data)
+        if res.status_code != 200:
+            log_response_error(res)
+            details = format_errors_dict(res.json())
+            raise CommunicationRequestError(f"Could not update dataset: {details}")
+        return res.json()
+
+    def get_mlcube_datasets(self, mlcube_id: int) -> dict:
+        """Retrieves all datasets that have the specified mlcube as the prep mlcube
+
+        Args:
+            mlcube_id (int): mlcube ID to retrieve datasets from
+
+        Returns:
+            dict: dictionary with the contents of each dataset
+        """
+
+        datasets = self.__get_list(f"{self.server_url}/mlcubes/{mlcube_id}/datasets/")
+        return datasets
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__get_list(url, num_elements=None, page_size=config.default_page_size, offset=0, binary_reduction=False) + +

+ + +
+ +

Retrieves a list of elements from a URL by iterating over pages until num_elements is obtained. +If num_elements is None, then iterates until all elements have been retrieved. +If binary_reduction is enabled, errors are assumed to be related to response size. In that case, +the page_size is reduced by half until a successful response is obtained or until page_size can't be +reduced anymore.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
url + str + +
+

The url to retrieve elements from

+
+
+ required +
num_elements + int + +
+

The desired number of elements to be retrieved. Defaults to None.

+
+
+ None +
page_size + int + +
+

Starting page size. Defaults to config.default_page_size.

+
+
+ config.default_page_size +
start_limit + int + +
+

The starting position for element retrieval. Defaults to 0.

+
+
+ required +
binary_reduction + bool + +
+

Wether to handle errors by halfing the page size. Defaults to False.

+
+
+ False +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ +
+

List[dict]: A list of dictionaries representing the retrieved elements.

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def __get_list(
+    self,
+    url,
+    num_elements=None,
+    page_size=config.default_page_size,
+    offset=0,
+    binary_reduction=False,
+):
+    """Retrieves a list of elements from a URL by iterating over pages until num_elements is obtained.
+    If num_elements is None, then iterates until all elements have been retrieved.
+    If binary_reduction is enabled, errors are assumed to be related to response size. In that case,
+    the page_size is reduced by half until a successful response is obtained or until page_size can't be
+    reduced anymore.
+
+    Args:
+        url (str): The url to retrieve elements from
+        num_elements (int, optional): The desired number of elements to be retrieved. Defaults to None.
+        page_size (int, optional): Starting page size. Defaults to config.default_page_size.
+        start_limit (int, optional): The starting position for element retrieval. Defaults to 0.
+        binary_reduction (bool, optional): Wether to handle errors by halfing the page size. Defaults to False.
+
+    Returns:
+        List[dict]: A list of dictionaries representing the retrieved elements.
+    """
+    el_list = []
+
+    if num_elements is None:
+        num_elements = float("inf")
+
+    while len(el_list) < num_elements:
+        paginated_url = f"{url}?limit={page_size}&offset={offset}"
+        res = self.__auth_get(paginated_url)
+        if res.status_code != 200:
+            if not binary_reduction:
+                log_response_error(res)
+                details = format_errors_dict(res.json())
+                raise CommunicationRetrievalError(
+                    f"there was an error retrieving the current list: {details}"
+                )
+
+            log_response_error(res, warn=True)
+            details = format_errors_dict(res.json())
+            if page_size <= 1:
+                raise CommunicationRetrievalError(
+                    f"Could not retrieve list. Minimum page size achieved without success: {details}"
+                )
+            page_size = page_size // 2
+            continue
+        else:
+            data = res.json()
+            el_list += data["results"]
+            offset += len(data["results"])
+            if data["next"] is None:
+                break
+
+    if isinstance(num_elements, int):
+        return el_list[:num_elements]
+    return el_list
+
+
+
+ +
+ + +
+ + + +

+__set_approval_status(url, status) + +

+ + +
+ +

Sets the approval status of a resource

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
url + str + +
+

URL to the resource to update

+
+
+ required +
status + str + +
+

approval status to set

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ requests.Response + +
+

requests.Response: Response object returned by the update

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def __set_approval_status(self, url: str, status: str) -> requests.Response:
+    """Sets the approval status of a resource
+
+    Args:
+        url (str): URL to the resource to update
+        status (str): approval status to set
+
+    Returns:
+        requests.Response: Response object returned by the update
+    """
+    data = {"approval_status": status}
+    res = self.__auth_put(url, json=data)
+    return res
+
+
+
+ +
+ + +
+ + + +

+associate_cube(cube_uid, benchmark_uid, metadata={}) + +

+ + +
+ +

Create an MLCube-Benchmark association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
cube_uid + int + +
+

MLCube UID

+
+
+ required +
benchmark_uid + int + +
+

Benchmark UID

+
+
+ required +
metadata + dict + +
+

Additional metadata. Defaults to {}.

+
+
+ {} +
+ +
+ Source code in cli/medperf/comms/rest.py +
def associate_cube(self, cube_uid: int, benchmark_uid: int, metadata: dict = {}):
+    """Create an MLCube-Benchmark association
+
+    Args:
+        cube_uid (int): MLCube UID
+        benchmark_uid (int): Benchmark UID
+        metadata (dict, optional): Additional metadata. Defaults to {}.
+    """
+    data = {
+        "approval_status": Status.PENDING.value,
+        "model_mlcube": cube_uid,
+        "benchmark": benchmark_uid,
+        "metadata": metadata,
+    }
+    res = self.__auth_post(f"{self.server_url}/mlcubes/benchmarks/", json=data)
+    if res.status_code != 201:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRequestError(
+            f"Could not associate mlcube to benchmark: {details}"
+        )
+
+
+
+ +
+ + +
+ + + +

+associate_dset(data_uid, benchmark_uid, metadata={}) + +

+ + +
+ +

Create a Dataset Benchmark association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
data_uid + int + +
+

Registered dataset UID

+
+
+ required +
benchmark_uid + int + +
+

Benchmark UID

+
+
+ required +
metadata + dict + +
+

Additional metadata. Defaults to {}.

+
+
+ {} +
+ +
+ Source code in cli/medperf/comms/rest.py +
def associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):
+    """Create a Dataset Benchmark association
+
+    Args:
+        data_uid (int): Registered dataset UID
+        benchmark_uid (int): Benchmark UID
+        metadata (dict, optional): Additional metadata. Defaults to {}.
+    """
+    data = {
+        "dataset": data_uid,
+        "benchmark": benchmark_uid,
+        "approval_status": Status.PENDING.value,
+        "metadata": metadata,
+    }
+    res = self.__auth_post(f"{self.server_url}/datasets/benchmarks/", json=data)
+    if res.status_code != 201:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRequestError(
+            f"Could not associate dataset to benchmark: {details}"
+        )
+
+
+
+ +
+ + +
+ + + +

+get_benchmark(benchmark_uid) + +

+ + +
+ +

Retrieves the benchmark specification file from the server

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

uid for the desired benchmark

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

benchmark specification

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_benchmark(self, benchmark_uid: int) -> dict:
+    """Retrieves the benchmark specification file from the server
+
+    Args:
+        benchmark_uid (int): uid for the desired benchmark
+
+    Returns:
+        dict: benchmark specification
+    """
+    res = self.__auth_get(f"{self.server_url}/benchmarks/{benchmark_uid}")
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRetrievalError(
+            f"the specified benchmark doesn't exist: {details}"
+        )
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+get_benchmark_model_associations(benchmark_uid) + +

+ + +
+ +

Retrieves all the model associations of a benchmark.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

UID of the desired benchmark

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[int] + +
+

list[int]: List of benchmark model associations

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:
+    """Retrieves all the model associations of a benchmark.
+
+    Args:
+        benchmark_uid (int): UID of the desired benchmark
+
+    Returns:
+        list[int]: List of benchmark model associations
+    """
+    assocs = self.__get_list(f"{self.server_url}/benchmarks/{benchmark_uid}/models")
+    return filter_latest_associations(assocs, "model_mlcube")
+
+
+
+ +
+ + +
+ + + +

+get_benchmark_results(benchmark_id) + +

+ + +
+ +

Retrieves all results for a given benchmark

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_id + int + +
+

benchmark ID to retrieve results from

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary with the contents of each result in the specified benchmark

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_benchmark_results(self, benchmark_id: int) -> dict:
+    """Retrieves all results for a given benchmark
+
+    Args:
+        benchmark_id (int): benchmark ID to retrieve results from
+
+    Returns:
+        dict: dictionary with the contents of each result in the specified benchmark
+    """
+    results = self.__get_list(
+        f"{self.server_url}/benchmarks/{benchmark_id}/results"
+    )
+    return results
+
+
+
+ +
+ + +
+ + + +

+get_benchmarks() + +

+ + +
+ +

Retrieves all benchmarks in the platform.

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: all benchmarks information.

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_benchmarks(self) -> List[dict]:
+    """Retrieves all benchmarks in the platform.
+
+    Returns:
+        List[dict]: all benchmarks information.
+    """
+    bmks = self.__get_list(f"{self.server_url}/benchmarks/")
+    return bmks
+
+
+
+ +
+ + +
+ + + +

+get_cube_metadata(cube_uid) + +

+ + +
+ +

Retrieves metadata about the specified cube

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
cube_uid + int + +
+

UID of the desired cube.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

Dictionary containing url and hashes for the cube files

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_cube_metadata(self, cube_uid: int) -> dict:
+    """Retrieves metadata about the specified cube
+
+    Args:
+        cube_uid (int): UID of the desired cube.
+
+    Returns:
+        dict: Dictionary containing url and hashes for the cube files
+    """
+    res = self.__auth_get(f"{self.server_url}/mlcubes/{cube_uid}/")
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRetrievalError(
+            f"the specified cube doesn't exist {details}"
+        )
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+get_cubes() + +

+ + +
+ +

Retrieves all MLCubes in the platform

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List containing the data of all MLCubes

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_cubes(self) -> List[dict]:
+    """Retrieves all MLCubes in the platform
+
+    Returns:
+        List[dict]: List containing the data of all MLCubes
+    """
+    cubes = self.__get_list(f"{self.server_url}/mlcubes/")
+    return cubes
+
+
+
+ +
+ + +
+ + + +

+get_cubes_associations() + +

+ + +
+ +

Get all cube associations related to the current user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List containing all associations information

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_cubes_associations(self) -> List[dict]:
+    """Get all cube associations related to the current user
+
+    Returns:
+        List[dict]: List containing all associations information
+    """
+    assocs = self.__get_list(f"{self.server_url}/me/mlcubes/associations/")
+    return filter_latest_associations(assocs, "model_mlcube")
+
+
+
+ +
+ + +
+ + + +

+get_current_user() + +

+ + +
+ +

Retrieve the currently-authenticated user information

+ +
+ Source code in cli/medperf/comms/rest.py +
def get_current_user(self):
+    """Retrieve the currently-authenticated user information"""
+    res = self.__auth_get(f"{self.server_url}/me/")
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+get_dataset(dset_uid) + +

+ + +
+ +

Retrieves a specific dataset

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dset_uid + int + +
+

Dataset UID

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

Dataset metadata

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_dataset(self, dset_uid: int) -> dict:
+    """Retrieves a specific dataset
+
+    Args:
+        dset_uid (int): Dataset UID
+
+    Returns:
+        dict: Dataset metadata
+    """
+    res = self.__auth_get(f"{self.server_url}/datasets/{dset_uid}/")
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRetrievalError(
+            f"Could not retrieve the specified dataset from server: {details}"
+        )
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+get_datasets() + +

+ + +
+ +

Retrieves all datasets in the platform

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List of data from all datasets

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_datasets(self) -> List[dict]:
+    """Retrieves all datasets in the platform
+
+    Returns:
+        List[dict]: List of data from all datasets
+    """
+    dsets = self.__get_list(f"{self.server_url}/datasets/")
+    return dsets
+
+
+
+ +
+ + +
+ + + +

+get_datasets_associations() + +

+ + +
+ +

Get all dataset associations related to the current user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List containing all associations information

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_datasets_associations(self) -> List[dict]:
+    """Get all dataset associations related to the current user
+
+    Returns:
+        List[dict]: List containing all associations information
+    """
+    assocs = self.__get_list(f"{self.server_url}/me/datasets/associations/")
+    return filter_latest_associations(assocs, "dataset")
+
+
+
+ +
+ + +
+ + + +

+get_mlcube_datasets(mlcube_id) + +

+ + +
+ +

Retrieves all datasets that have the specified mlcube as the prep mlcube

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
mlcube_id + int + +
+

mlcube ID to retrieve datasets from

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary with the contents of each dataset

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_mlcube_datasets(self, mlcube_id: int) -> dict:
+    """Retrieves all datasets that have the specified mlcube as the prep mlcube
+
+    Args:
+        mlcube_id (int): mlcube ID to retrieve datasets from
+
+    Returns:
+        dict: dictionary with the contents of each dataset
+    """
+
+    datasets = self.__get_list(f"{self.server_url}/mlcubes/{mlcube_id}/datasets/")
+    return datasets
+
+
+
+ +
+ + +
+ + + +

+get_result(result_uid) + +

+ + +
+ +

Retrieves a specific result data

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
result_uid + int + +
+

Result UID

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

Result metadata

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_result(self, result_uid: int) -> dict:
+    """Retrieves a specific result data
+
+    Args:
+        result_uid (int): Result UID
+
+    Returns:
+        dict: Result metadata
+    """
+    res = self.__auth_get(f"{self.server_url}/results/{result_uid}/")
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRetrievalError(
+            f"Could not retrieve the specified result: {details}"
+        )
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+get_results() + +

+ + +
+ +

Retrieves all results

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List of results

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_results(self) -> List[dict]:
+    """Retrieves all results
+
+    Returns:
+        List[dict]: List of results
+    """
+    res = self.__get_list(f"{self.server_url}/results")
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRetrievalError(f"Could not retrieve results: {details}")
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+get_user_benchmarks() + +

+ + +
+ +

Retrieves all benchmarks created by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: Benchmarks data

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_user_benchmarks(self) -> List[dict]:
+    """Retrieves all benchmarks created by the user
+
+    Returns:
+        List[dict]: Benchmarks data
+    """
+    bmks = self.__get_list(f"{self.server_url}/me/benchmarks/")
+    return bmks
+
+
+
+ +
+ + +
+ + + +

+get_user_cubes() + +

+ + +
+ +

Retrieves metadata from all cubes registered by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[dict] + +
+

List[dict]: List of dictionaries containing the mlcubes registration information

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_user_cubes(self) -> List[dict]:
+    """Retrieves metadata from all cubes registered by the user
+
+    Returns:
+        List[dict]: List of dictionaries containing the mlcubes registration information
+    """
+    cubes = self.__get_list(f"{self.server_url}/me/mlcubes/")
+    return cubes
+
+
+
+ +
+ + +
+ + + +

+get_user_datasets() + +

+ + +
+ +

Retrieves all datasets registered by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary with the contents of each dataset registration query

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_user_datasets(self) -> dict:
+    """Retrieves all datasets registered by the user
+
+    Returns:
+        dict: dictionary with the contents of each dataset registration query
+    """
+    dsets = self.__get_list(f"{self.server_url}/me/datasets/")
+    return dsets
+
+
+
+ +
+ + +
+ + + +

+get_user_results() + +

+ + +
+ +

Retrieves all results registered by the user

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

dictionary with the contents of each result registration query

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def get_user_results(self) -> dict:
+    """Retrieves all results registered by the user
+
+    Returns:
+        dict: dictionary with the contents of each result registration query
+    """
+    results = self.__get_list(f"{self.server_url}/me/results/")
+    return results
+
+
+
+ +
+ + +
+ + + +

+parse_url(url) + + + classmethod + + +

+ + +
+ +

Parse the source URL so that it can be used by the comms implementation. +It should handle protocols and versioning to be able to communicate with the API.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
url + str + +
+

base URL

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

parsed URL with protocol and version

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
@classmethod
+def parse_url(cls, url: str) -> str:
+    """Parse the source URL so that it can be used by the comms implementation.
+    It should handle protocols and versioning to be able to communicate with the API.
+
+    Args:
+        url (str): base URL
+
+    Returns:
+        str: parsed URL with protocol and version
+    """
+    url_sections = url.split("://")
+    api_path = f"/api/v{config.major_version}"
+    # Remove protocol if passed
+    if len(url_sections) > 1:
+        url = "".join(url_sections[1:])
+
+    return f"https://{url}{api_path}"
+
+
+
+ +
+ + +
+ + + +

+set_dataset_association_approval(benchmark_uid, dataset_uid, status) + +

+ + +
+ +

Approves a dataset association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dataset_uid + int + +
+

Dataset UID

+
+
+ required +
benchmark_uid + int + +
+

Benchmark UID

+
+
+ required +
status + str + +
+

Approval status to set for the association

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/rest.py +
def set_dataset_association_approval(
+    self, benchmark_uid: int, dataset_uid: int, status: str
+):
+    """Approves a dataset association
+
+    Args:
+        dataset_uid (int): Dataset UID
+        benchmark_uid (int): Benchmark UID
+        status (str): Approval status to set for the association
+    """
+    url = f"{self.server_url}/datasets/{dataset_uid}/benchmarks/{benchmark_uid}/"
+    res = self.__set_approval_status(url, status)
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRequestError(
+            f"Could not approve association between dataset {dataset_uid} and benchmark {benchmark_uid}: {details}"
+        )
+
+
+
+ +
+ + +
+ + + +

+set_mlcube_association_approval(benchmark_uid, mlcube_uid, status) + +

+ + +
+ +

Approves an mlcube association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
mlcube_uid + int + +
+

Dataset UID

+
+
+ required +
benchmark_uid + int + +
+

Benchmark UID

+
+
+ required +
status + str + +
+

Approval status to set for the association

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/rest.py +
def set_mlcube_association_approval(
+    self, benchmark_uid: int, mlcube_uid: int, status: str
+):
+    """Approves an mlcube association
+
+    Args:
+        mlcube_uid (int): Dataset UID
+        benchmark_uid (int): Benchmark UID
+        status (str): Approval status to set for the association
+    """
+    url = f"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/"
+    res = self.__set_approval_status(url, status)
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRequestError(
+            f"Could not approve association between mlcube {mlcube_uid} and benchmark {benchmark_uid}: {details}"
+        )
+
+
+
+ +
+ + +
+ + + +

+set_mlcube_association_priority(benchmark_uid, mlcube_uid, priority) + +

+ + +
+ +

Sets the priority of an mlcube-benchmark association

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
mlcube_uid + int + +
+

MLCube UID

+
+
+ required +
benchmark_uid + int + +
+

Benchmark UID

+
+
+ required +
priority + int + +
+

priority value to set for the association

+
+
+ required +
+ +
+ Source code in cli/medperf/comms/rest.py +
def set_mlcube_association_priority(
+    self, benchmark_uid: int, mlcube_uid: int, priority: int
+):
+    """Sets the priority of an mlcube-benchmark association
+
+    Args:
+        mlcube_uid (int): MLCube UID
+        benchmark_uid (int): Benchmark UID
+        priority (int): priority value to set for the association
+    """
+    url = f"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/"
+    data = {"priority": priority}
+    res = self.__auth_put(url, json=data)
+    if res.status_code != 200:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRequestError(
+            f"Could not set the priority of mlcube {mlcube_uid} within the benchmark {benchmark_uid}: {details}"
+        )
+
+
+
+ +
+ + +
+ + + +

+upload_benchmark(benchmark_dict) + +

+ + +
+ +

Uploads a new benchmark to the server.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_dict + dict + +
+

benchmark_data to be uploaded

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

UID of newly created benchmark

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def upload_benchmark(self, benchmark_dict: dict) -> int:
+    """Uploads a new benchmark to the server.
+
+    Args:
+        benchmark_dict (dict): benchmark_data to be uploaded
+
+    Returns:
+        int: UID of newly created benchmark
+    """
+    res = self.__auth_post(f"{self.server_url}/benchmarks/", json=benchmark_dict)
+    if res.status_code != 201:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRetrievalError(f"Could not upload benchmark: {details}")
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+upload_dataset(reg_dict) + +

+ + +
+ +

Uploads registration data to the server, under the sha name of the file.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
reg_dict + dict + +
+

Dictionary containing registration information.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

id of the created dataset registration.

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def upload_dataset(self, reg_dict: dict) -> int:
+    """Uploads registration data to the server, under the sha name of the file.
+
+    Args:
+        reg_dict (dict): Dictionary containing registration information.
+
+    Returns:
+        int: id of the created dataset registration.
+    """
+    res = self.__auth_post(f"{self.server_url}/datasets/", json=reg_dict)
+    if res.status_code != 201:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRequestError(f"Could not upload the dataset: {details}")
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+upload_mlcube(mlcube_body) + +

+ + +
+ +

Uploads an MLCube instance to the platform

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
mlcube_body + dict + +
+

Dictionary containing all the relevant data for creating mlcubes

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

id of the created mlcube instance on the platform

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def upload_mlcube(self, mlcube_body: dict) -> int:
+    """Uploads an MLCube instance to the platform
+
+    Args:
+        mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes
+
+    Returns:
+        int: id of the created mlcube instance on the platform
+    """
+    res = self.__auth_post(f"{self.server_url}/mlcubes/", json=mlcube_body)
+    if res.status_code != 201:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRetrievalError(f"Could not upload the mlcube: {details}")
+    return res.json()
+
+
+
+ +
+ + +
+ + + +

+upload_result(results_dict) + +

+ + +
+ +

Uploads result to the server.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
results_dict + dict + +
+

Dictionary containing results information.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
int + int + +
+

id of the generated results entry

+
+
+ +
+ Source code in cli/medperf/comms/rest.py +
def upload_result(self, results_dict: dict) -> int:
+    """Uploads result to the server.
+
+    Args:
+        results_dict (dict): Dictionary containing results information.
+
+    Returns:
+        int: id of the generated results entry
+    """
+    res = self.__auth_post(f"{self.server_url}/results/", json=results_dict)
+    if res.status_code != 201:
+        log_response_error(res)
+        details = format_errors_dict(res.json())
+        raise CommunicationRequestError(f"Could not upload the results: {details}")
+    return res.json()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/config/index.html b/reference/config/index.html new file mode 100644 index 000000000..ab2c6980c --- /dev/null +++ b/reference/config/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Config - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Config

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/config_management/config_management/index.html b/reference/config_management/config_management/index.html new file mode 100644 index 000000000..74e904ccd --- /dev/null +++ b/reference/config_management/config_management/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Config management - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Config management

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/decorators/index.html b/reference/decorators/index.html new file mode 100644 index 000000000..47d954113 --- /dev/null +++ b/reference/decorators/index.html @@ -0,0 +1,1594 @@ + + + + + + + + + + + + + + + + + + + + Decorators - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Decorators

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+add_inline_parameters(func) + +

+ + +
+ +

Decorator that adds common configuration options to a typer command

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
func + Callable + +
+

function to be decorated

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Callable + Callable + +
+

decorated function

+
+
+ +
+ Source code in cli/medperf/decorators.py +
def add_inline_parameters(func: Callable) -> Callable:
+    """Decorator that adds common configuration options to a typer command
+
+    Args:
+        func (Callable): function to be decorated
+
+    Returns:
+        Callable: decorated function
+    """
+
+    # NOTE: changing parameters here should be accompanied
+    #       by changing config.inline_parameters
+    @merge_args(func)
+    def wrapper(
+        *args,
+        loglevel: str = typer.Option(
+            config.loglevel,
+            "--loglevel",
+            help="Logging level [debug | info | warning | error]",
+        ),
+        prepare_timeout: int = typer.Option(
+            config.prepare_timeout,
+            "--prepare_timeout",
+            help="Maximum time in seconds before interrupting prepare task",
+        ),
+        sanity_check_timeout: int = typer.Option(
+            config.sanity_check_timeout,
+            "--sanity_check_timeout",
+            help="Maximum time in seconds before interrupting sanity_check task",
+        ),
+        statistics_timeout: int = typer.Option(
+            config.statistics_timeout,
+            "--statistics_timeout",
+            help="Maximum time in seconds before interrupting statistics task",
+        ),
+        infer_timeout: int = typer.Option(
+            config.infer_timeout,
+            "--infer_timeout",
+            help="Maximum time in seconds before interrupting infer task",
+        ),
+        evaluate_timeout: int = typer.Option(
+            config.evaluate_timeout,
+            "--evaluate_timeout",
+            help="Maximum time in seconds before interrupting evaluate task",
+        ),
+        container_loglevel: str = typer.Option(
+            config.container_loglevel,
+            "--container-loglevel",
+            help="Logging level for containers to be run [debug | info | warning | error]",
+        ),
+        platform: str = typer.Option(
+            config.platform,
+            "--platform",
+            help="Platform to use for MLCube. [docker | singularity]",
+        ),
+        gpus: str = typer.Option(
+            config.gpus,
+            "--gpus",
+            help="""
+            What GPUs to expose to MLCube.
+            Accepted Values are:\n
+            - "" or 0: to expose no GPUs (e.g.: --gpus="")\n
+            - "all": to expose all GPUs. (e.g.: --gpus=all)\n
+            - an integer: to expose a certain number of GPUs. ONLY AVAILABLE FOR DOCKER
+            (e.g., --gpus=2 to expose 2 GPUs)\n
+            - Form "device=<id1>,<id2>": to expose specific GPUs.
+            (e.g., --gpus="device=0,2")\n""",
+        ),
+        cleanup: bool = typer.Option(
+            config.cleanup,
+            "--cleanup/--no-cleanup",
+            help="Whether to clean up temporary medperf storage after execution",
+        ),
+        **kwargs,
+    ):
+        return func(*args, **kwargs)
+
+    return wrapper
+
+
+
+ +
+ + +
+ + + +

+clean_except(func) + +

+ + +
+ +

Decorator for handling errors. It allows logging +and cleaning the project's directory before throwing the error.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
func + Callable + +
+

Function to handle for errors

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Callable + Callable + +
+

Decorated function

+
+
+ +
+ Source code in cli/medperf/decorators.py +
13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
def clean_except(func: Callable) -> Callable:
+    """Decorator for handling errors. It allows logging
+    and cleaning the project's directory before throwing the error.
+
+    Args:
+        func (Callable): Function to handle for errors
+
+    Returns:
+        Callable: Decorated function
+    """
+
+    @functools.wraps(func)
+    def wrapper(*args, **kwargs):
+        try:
+            logging.info(f"Running function '{func.__name__}'")
+            func(*args, **kwargs)
+        except CleanExit as e:
+            logging.info(str(e))
+            config.ui.print(str(e))
+            sys.exit(e.medperf_status_code)
+        except MedperfException as e:
+            logging.exception(e)
+            pretty_error(str(e))
+            sys.exit(1)
+        except Exception as e:
+            logging.error("An unexpected error occured. Terminating.")
+            logging.exception(e)
+            raise e
+        finally:
+            package_logs()
+            cleanup()
+
+    return wrapper
+
+
+
+ +
+ + +
+ + + +

+configurable(func) + +

+ + +
+ +

Decorator that adds common configuration options to a typer command

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
func + Callable + +
+

function to be decorated

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Callable + Callable + +
+

decorated function

+
+
+ +
+ Source code in cli/medperf/decorators.py +
def configurable(func: Callable) -> Callable:
+    """Decorator that adds common configuration options to a typer command
+
+    Args:
+        func (Callable): function to be decorated
+
+    Returns:
+        Callable: decorated function
+    """
+
+    # NOTE: changing parameters here should be accompanied
+    #       by changing configurable_parameters
+    @merge_args(func)
+    def wrapper(
+        *args,
+        server: str = typer.Option(
+            config.server, "--server", help="URL of a hosted MedPerf API instance"
+        ),
+        auth_class: str = typer.Option(
+            config.auth_class,
+            "--auth_class",
+            help="Authentication interface to use [Auth0]",
+        ),
+        auth_domain: str = typer.Option(
+            config.auth_domain, "--auth_domain", help="Auth0 domain name"
+        ),
+        auth_jwks_url: str = typer.Option(
+            config.auth_jwks_url, "--auth_jwks_url", help="Auth0 Json Web Key set URL"
+        ),
+        auth_idtoken_issuer: str = typer.Option(
+            config.auth_idtoken_issuer,
+            "--auth_idtoken_issuer",
+            help="Auth0 ID token issuer",
+        ),
+        auth_client_id: str = typer.Option(
+            config.auth_client_id, "--auth_client_id", help="Auth0 client ID"
+        ),
+        auth_audience: str = typer.Option(
+            config.auth_audience,
+            "--auth_audience",
+            help="Server's Auth0 API identifier",
+        ),
+        certificate: str = typer.Option(
+            config.certificate, "--certificate", help="path to a valid SSL certificate"
+        ),
+        loglevel: str = typer.Option(
+            config.loglevel,
+            "--loglevel",
+            help="Logging level [debug | info | warning | error]",
+        ),
+        prepare_timeout: int = typer.Option(
+            config.prepare_timeout,
+            "--prepare_timeout",
+            help="Maximum time in seconds before interrupting prepare task",
+        ),
+        sanity_check_timeout: int = typer.Option(
+            config.sanity_check_timeout,
+            "--sanity_check_timeout",
+            help="Maximum time in seconds before interrupting sanity_check task",
+        ),
+        statistics_timeout: int = typer.Option(
+            config.statistics_timeout,
+            "--statistics_timeout",
+            help="Maximum time in seconds before interrupting statistics task",
+        ),
+        infer_timeout: int = typer.Option(
+            config.infer_timeout,
+            "--infer_timeout",
+            help="Maximum time in seconds before interrupting infer task",
+        ),
+        evaluate_timeout: int = typer.Option(
+            config.evaluate_timeout,
+            "--evaluate_timeout",
+            help="Maximum time in seconds before interrupting evaluate task",
+        ),
+        container_loglevel: str = typer.Option(
+            config.container_loglevel,
+            "--container-loglevel",
+            help="Logging level for containers to be run [debug | info | warning | error]",
+        ),
+        platform: str = typer.Option(
+            config.platform,
+            "--platform",
+            help="Platform to use for MLCube. [docker | singularity]",
+        ),
+        gpus: str = typer.Option(
+            config.gpus,
+            "--gpus",
+            help="""
+            What GPUs to expose to MLCube.
+            Accepted Values are comma separated GPU IDs (e.g "1,2"), or \"all\".
+            MLCubes that aren't configured to use GPUs won't be affected by this.
+            Defaults to all available GPUs""",
+        ),
+        cleanup: bool = typer.Option(
+            config.cleanup,
+            "--cleanup/--no-cleanup",
+            help="Wether to clean up temporary medperf storage after execution",
+        ),
+        **kwargs,
+    ):
+        return func(*args, **kwargs)
+
+    return wrapper
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/entities/benchmark/index.html b/reference/entities/benchmark/index.html new file mode 100644 index 000000000..e73d9a1c1 --- /dev/null +++ b/reference/entities/benchmark/index.html @@ -0,0 +1,2742 @@ + + + + + + + + + + + + + + + + + + + + Benchmark - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Benchmark

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Benchmark + + +

+ + +
+

+ Bases: Entity, Uploadable, MedperfSchema, ApprovableSchema, DeployableSchema

+ + +

Class representing a Benchmark

+

a benchmark is a bundle of assets that enables quantitative +measurement of the performance of AI models for a specific +clinical problem. A Benchmark instance contains information +regarding how to prepare datasets for execution, as well as +what models to run and how to evaluate them.

+ +
+ Source code in cli/medperf/entities/benchmark.py +
class Benchmark(Entity, Uploadable, MedperfSchema, ApprovableSchema, DeployableSchema):
+    """
+    Class representing a Benchmark
+
+    a benchmark is a bundle of assets that enables quantitative
+    measurement of the performance of AI models for a specific
+    clinical problem. A Benchmark instance contains information
+    regarding how to prepare datasets for execution, as well as
+    what models to run and how to evaluate them.
+    """
+
+    description: Optional[str] = Field(None, max_length=20)
+    docs_url: Optional[HttpUrl]
+    demo_dataset_tarball_url: str
+    demo_dataset_tarball_hash: Optional[str]
+    demo_dataset_generated_uid: Optional[str]
+    data_preparation_mlcube: int
+    reference_model_mlcube: int
+    data_evaluator_mlcube: int
+    metadata: dict = {}
+    user_metadata: dict = {}
+    is_active: bool = True
+
+    def __init__(self, *args, **kwargs):
+        """Creates a new benchmark instance
+
+        Args:
+            bmk_desc (Union[dict, BenchmarkModel]): Benchmark instance description
+        """
+        super().__init__(*args, **kwargs)
+
+        self.generated_uid = f"p{self.data_preparation_mlcube}m{self.reference_model_mlcube}e{self.data_evaluator_mlcube}"
+        path = config.benchmarks_folder
+        if self.id:
+            path = os.path.join(path, str(self.id))
+        else:
+            path = os.path.join(path, self.generated_uid)
+        self.path = path
+
+    @classmethod
+    def all(cls, local_only: bool = False, filters: dict = {}) -> List["Benchmark"]:
+        """Gets and creates instances of all retrievable benchmarks
+
+        Args:
+            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+        Returns:
+            List[Benchmark]: a list of Benchmark instances.
+        """
+        logging.info("Retrieving all benchmarks")
+        benchmarks = []
+
+        if not local_only:
+            benchmarks = cls.__remote_all(filters=filters)
+
+        remote_uids = set([bmk.id for bmk in benchmarks])
+
+        local_benchmarks = cls.__local_all()
+
+        benchmarks += [bmk for bmk in local_benchmarks if bmk.id not in remote_uids]
+
+        return benchmarks
+
+    @classmethod
+    def __remote_all(cls, filters: dict) -> List["Benchmark"]:
+        benchmarks = []
+        try:
+            comms_fn = cls.__remote_prefilter(filters)
+            bmks_meta = comms_fn()
+            benchmarks = [cls(**meta) for meta in bmks_meta]
+        except CommunicationRetrievalError:
+            msg = "Couldn't retrieve all benchmarks from the server"
+            logging.warning(msg)
+
+        return benchmarks
+
+    @classmethod
+    def __remote_prefilter(cls, filters: dict) -> callable:
+        """Applies filtering logic that must be done before retrieving remote entities
+
+        Args:
+            filters (dict): filters to apply
+
+        Returns:
+            callable: A function for retrieving remote entities with the applied prefilters
+        """
+        comms_fn = config.comms.get_benchmarks
+        if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+            comms_fn = config.comms.get_user_benchmarks
+        return comms_fn
+
+    @classmethod
+    def __local_all(cls) -> List["Benchmark"]:
+        benchmarks = []
+        bmks_storage = config.benchmarks_folder
+        try:
+            uids = next(os.walk(bmks_storage))[1]
+        except StopIteration:
+            msg = "Couldn't iterate over benchmarks directory"
+            logging.warning(msg)
+            raise MedperfException(msg)
+
+        for uid in uids:
+            meta = cls.__get_local_dict(uid)
+            benchmark = cls(**meta)
+            benchmarks.append(benchmark)
+
+        return benchmarks
+
+    @classmethod
+    def get(
+        cls, benchmark_uid: Union[str, int], local_only: bool = False
+    ) -> "Benchmark":
+        """Retrieves and creates a Benchmark instance from the server.
+        If benchmark already exists in the platform then retrieve that
+        version.
+
+        Args:
+            benchmark_uid (str): UID of the benchmark.
+            comms (Comms): Instance of a communication interface.
+
+        Returns:
+            Benchmark: a Benchmark instance with the retrieved data.
+        """
+
+        if not str(benchmark_uid).isdigit() or local_only:
+            return cls.__local_get(benchmark_uid)
+
+        try:
+            return cls.__remote_get(benchmark_uid)
+        except CommunicationRetrievalError:
+            logging.warning(f"Getting Benchmark {benchmark_uid} from comms failed")
+            logging.info(f"Looking for benchmark {benchmark_uid} locally")
+            return cls.__local_get(benchmark_uid)
+
+    @classmethod
+    def __remote_get(cls, benchmark_uid: int) -> "Benchmark":
+        """Retrieves and creates a Dataset instance from the comms instance.
+        If the dataset is present in the user's machine then it retrieves it from there.
+
+        Args:
+            dset_uid (str): server UID of the dataset
+
+        Returns:
+            Dataset: Specified Dataset Instance
+        """
+        logging.debug(f"Retrieving benchmark {benchmark_uid} remotely")
+        benchmark_dict = config.comms.get_benchmark(benchmark_uid)
+        benchmark = cls(**benchmark_dict)
+        benchmark.write()
+        return benchmark
+
+    @classmethod
+    def __local_get(cls, benchmark_uid: Union[str, int]) -> "Benchmark":
+        """Retrieves and creates a Dataset instance from the comms instance.
+        If the dataset is present in the user's machine then it retrieves it from there.
+
+        Args:
+            dset_uid (str): server UID of the dataset
+
+        Returns:
+            Dataset: Specified Dataset Instance
+        """
+        logging.debug(f"Retrieving benchmark {benchmark_uid} locally")
+        benchmark_dict = cls.__get_local_dict(benchmark_uid)
+        benchmark = cls(**benchmark_dict)
+        return benchmark
+
+    @classmethod
+    def __get_local_dict(cls, benchmark_uid) -> dict:
+        """Retrieves a local benchmark information
+
+        Args:
+            benchmark_uid (str): uid of the local benchmark
+
+        Returns:
+            dict: information of the benchmark
+        """
+        logging.info(f"Retrieving benchmark {benchmark_uid} from local storage")
+        storage = config.benchmarks_folder
+        bmk_storage = os.path.join(storage, str(benchmark_uid))
+        bmk_file = os.path.join(bmk_storage, config.benchmarks_filename)
+        if not os.path.exists(bmk_file):
+            raise InvalidArgumentError("No benchmark with the given uid could be found")
+        with open(bmk_file, "r") as f:
+            data = yaml.safe_load(f)
+
+        return data
+
+    @classmethod
+    def get_models_uids(cls, benchmark_uid: int) -> List[int]:
+        """Retrieves the list of models associated to the benchmark
+
+        Args:
+            benchmark_uid (int): UID of the benchmark.
+            comms (Comms): Instance of the communications interface.
+
+        Returns:
+            List[int]: List of mlcube uids
+        """
+        associations = config.comms.get_benchmark_model_associations(benchmark_uid)
+        models_uids = [
+            assoc["model_mlcube"]
+            for assoc in associations
+            if assoc["approval_status"] == "APPROVED"
+        ]
+        return models_uids
+
+    def todict(self) -> dict:
+        """Dictionary representation of the benchmark instance
+
+        Returns:
+        dict: Dictionary containing benchmark information
+        """
+        return self.extended_dict()
+
+    def write(self) -> str:
+        """Writes the benchmark into disk
+
+        Args:
+            filename (str, optional): name of the file. Defaults to config.benchmarks_filename.
+
+        Returns:
+            str: path to the created benchmark file
+        """
+        data = self.todict()
+        bmk_file = os.path.join(self.path, config.benchmarks_filename)
+        if not os.path.exists(bmk_file):
+            os.makedirs(self.path, exist_ok=True)
+        with open(bmk_file, "w") as f:
+            yaml.dump(data, f)
+        return bmk_file
+
+    def upload(self):
+        """Uploads a benchmark to the server
+
+        Args:
+            comms (Comms): communications entity to submit through
+        """
+        if self.for_test:
+            raise InvalidArgumentError("Cannot upload test benchmarks.")
+        body = self.todict()
+        updated_body = config.comms.upload_benchmark(body)
+        return updated_body
+
+    def display_dict(self):
+        return {
+            "UID": self.identifier,
+            "Name": self.name,
+            "Description": self.description,
+            "Documentation": self.docs_url,
+            "Created At": self.created_at,
+            "Data Preparation MLCube": int(self.data_preparation_mlcube),
+            "Reference Model MLCube": int(self.reference_model_mlcube),
+            "Data Evaluator MLCube": int(self.data_evaluator_mlcube),
+            "State": self.state,
+            "Approval Status": self.approval_status,
+            "Registered": self.is_registered,
+        }
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__get_local_dict(benchmark_uid) + + + classmethod + + +

+ + +
+ +

Retrieves a local benchmark information

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + str + +
+

uid of the local benchmark

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

information of the benchmark

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
@classmethod
+def __get_local_dict(cls, benchmark_uid) -> dict:
+    """Retrieves a local benchmark information
+
+    Args:
+        benchmark_uid (str): uid of the local benchmark
+
+    Returns:
+        dict: information of the benchmark
+    """
+    logging.info(f"Retrieving benchmark {benchmark_uid} from local storage")
+    storage = config.benchmarks_folder
+    bmk_storage = os.path.join(storage, str(benchmark_uid))
+    bmk_file = os.path.join(bmk_storage, config.benchmarks_filename)
+    if not os.path.exists(bmk_file):
+        raise InvalidArgumentError("No benchmark with the given uid could be found")
+    with open(bmk_file, "r") as f:
+        data = yaml.safe_load(f)
+
+    return data
+
+
+
+ +
+ + +
+ + + +

+__init__(*args, **kwargs) + +

+ + +
+ +

Creates a new benchmark instance

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
bmk_desc + Union[dict, BenchmarkModel] + +
+

Benchmark instance description

+
+
+ required +
+ +
+ Source code in cli/medperf/entities/benchmark.py +
38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
def __init__(self, *args, **kwargs):
+    """Creates a new benchmark instance
+
+    Args:
+        bmk_desc (Union[dict, BenchmarkModel]): Benchmark instance description
+    """
+    super().__init__(*args, **kwargs)
+
+    self.generated_uid = f"p{self.data_preparation_mlcube}m{self.reference_model_mlcube}e{self.data_evaluator_mlcube}"
+    path = config.benchmarks_folder
+    if self.id:
+        path = os.path.join(path, str(self.id))
+    else:
+        path = os.path.join(path, self.generated_uid)
+    self.path = path
+
+
+
+ +
+ + +
+ + + +

+__local_get(benchmark_uid) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Dataset instance from the comms instance. +If the dataset is present in the user's machine then it retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dset_uid + str + +
+

server UID of the dataset

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dataset + Benchmark + +
+

Specified Dataset Instance

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
@classmethod
+def __local_get(cls, benchmark_uid: Union[str, int]) -> "Benchmark":
+    """Retrieves and creates a Dataset instance from the comms instance.
+    If the dataset is present in the user's machine then it retrieves it from there.
+
+    Args:
+        dset_uid (str): server UID of the dataset
+
+    Returns:
+        Dataset: Specified Dataset Instance
+    """
+    logging.debug(f"Retrieving benchmark {benchmark_uid} locally")
+    benchmark_dict = cls.__get_local_dict(benchmark_uid)
+    benchmark = cls(**benchmark_dict)
+    return benchmark
+
+
+
+ +
+ + +
+ + + +

+__remote_get(benchmark_uid) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Dataset instance from the comms instance. +If the dataset is present in the user's machine then it retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dset_uid + str + +
+

server UID of the dataset

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dataset + Benchmark + +
+

Specified Dataset Instance

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
@classmethod
+def __remote_get(cls, benchmark_uid: int) -> "Benchmark":
+    """Retrieves and creates a Dataset instance from the comms instance.
+    If the dataset is present in the user's machine then it retrieves it from there.
+
+    Args:
+        dset_uid (str): server UID of the dataset
+
+    Returns:
+        Dataset: Specified Dataset Instance
+    """
+    logging.debug(f"Retrieving benchmark {benchmark_uid} remotely")
+    benchmark_dict = config.comms.get_benchmark(benchmark_uid)
+    benchmark = cls(**benchmark_dict)
+    benchmark.write()
+    return benchmark
+
+
+
+ +
+ + +
+ + + +

+__remote_prefilter(filters) + + + classmethod + + +

+ + +
+ +

Applies filtering logic that must be done before retrieving remote entities

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filters + dict + +
+

filters to apply

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
callable + callable + +
+

A function for retrieving remote entities with the applied prefilters

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
@classmethod
+def __remote_prefilter(cls, filters: dict) -> callable:
+    """Applies filtering logic that must be done before retrieving remote entities
+
+    Args:
+        filters (dict): filters to apply
+
+    Returns:
+        callable: A function for retrieving remote entities with the applied prefilters
+    """
+    comms_fn = config.comms.get_benchmarks
+    if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+        comms_fn = config.comms.get_user_benchmarks
+    return comms_fn
+
+
+
+ +
+ + +
+ + + +

+all(local_only=False, filters={}) + + + classmethod + + +

+ + +
+ +

Gets and creates instances of all retrievable benchmarks

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
local_only + bool + +
+

Wether to retrieve only local entities. Defaults to False.

+
+
+ False +
filters + dict + +
+

key-value pairs specifying filters to apply to the list of entities.

+
+
+ {} +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[Benchmark] + +
+

List[Benchmark]: a list of Benchmark instances.

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
@classmethod
+def all(cls, local_only: bool = False, filters: dict = {}) -> List["Benchmark"]:
+    """Gets and creates instances of all retrievable benchmarks
+
+    Args:
+        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+    Returns:
+        List[Benchmark]: a list of Benchmark instances.
+    """
+    logging.info("Retrieving all benchmarks")
+    benchmarks = []
+
+    if not local_only:
+        benchmarks = cls.__remote_all(filters=filters)
+
+    remote_uids = set([bmk.id for bmk in benchmarks])
+
+    local_benchmarks = cls.__local_all()
+
+    benchmarks += [bmk for bmk in local_benchmarks if bmk.id not in remote_uids]
+
+    return benchmarks
+
+
+
+ +
+ + +
+ + + +

+get(benchmark_uid, local_only=False) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Benchmark instance from the server. +If benchmark already exists in the platform then retrieve that +version.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + str + +
+

UID of the benchmark.

+
+
+ required +
comms + Comms + +
+

Instance of a communication interface.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Benchmark + Benchmark + +
+

a Benchmark instance with the retrieved data.

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
@classmethod
+def get(
+    cls, benchmark_uid: Union[str, int], local_only: bool = False
+) -> "Benchmark":
+    """Retrieves and creates a Benchmark instance from the server.
+    If benchmark already exists in the platform then retrieve that
+    version.
+
+    Args:
+        benchmark_uid (str): UID of the benchmark.
+        comms (Comms): Instance of a communication interface.
+
+    Returns:
+        Benchmark: a Benchmark instance with the retrieved data.
+    """
+
+    if not str(benchmark_uid).isdigit() or local_only:
+        return cls.__local_get(benchmark_uid)
+
+    try:
+        return cls.__remote_get(benchmark_uid)
+    except CommunicationRetrievalError:
+        logging.warning(f"Getting Benchmark {benchmark_uid} from comms failed")
+        logging.info(f"Looking for benchmark {benchmark_uid} locally")
+        return cls.__local_get(benchmark_uid)
+
+
+
+ +
+ + +
+ + + +

+get_models_uids(benchmark_uid) + + + classmethod + + +

+ + +
+ +

Retrieves the list of models associated to the benchmark

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
benchmark_uid + int + +
+

UID of the benchmark.

+
+
+ required +
comms + Comms + +
+

Instance of the communications interface.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[int] + +
+

List[int]: List of mlcube uids

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
@classmethod
+def get_models_uids(cls, benchmark_uid: int) -> List[int]:
+    """Retrieves the list of models associated to the benchmark
+
+    Args:
+        benchmark_uid (int): UID of the benchmark.
+        comms (Comms): Instance of the communications interface.
+
+    Returns:
+        List[int]: List of mlcube uids
+    """
+    associations = config.comms.get_benchmark_model_associations(benchmark_uid)
+    models_uids = [
+        assoc["model_mlcube"]
+        for assoc in associations
+        if assoc["approval_status"] == "APPROVED"
+    ]
+    return models_uids
+
+
+
+ +
+ + +
+ + + +

+todict() + +

+ + +
+ +

Dictionary representation of the benchmark instance

+

dict: Dictionary containing benchmark information

+ +
+ Source code in cli/medperf/entities/benchmark.py +
def todict(self) -> dict:
+    """Dictionary representation of the benchmark instance
+
+    Returns:
+    dict: Dictionary containing benchmark information
+    """
+    return self.extended_dict()
+
+
+
+ +
+ + +
+ + + +

+upload() + +

+ + +
+ +

Uploads a benchmark to the server

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
comms + Comms + +
+

communications entity to submit through

+
+
+ required +
+ +
+ Source code in cli/medperf/entities/benchmark.py +
def upload(self):
+    """Uploads a benchmark to the server
+
+    Args:
+        comms (Comms): communications entity to submit through
+    """
+    if self.for_test:
+        raise InvalidArgumentError("Cannot upload test benchmarks.")
+    body = self.todict()
+    updated_body = config.comms.upload_benchmark(body)
+    return updated_body
+
+
+
+ +
+ + +
+ + + +

+write() + +

+ + +
+ +

Writes the benchmark into disk

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filename + str + +
+

name of the file. Defaults to config.benchmarks_filename.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

path to the created benchmark file

+
+
+ +
+ Source code in cli/medperf/entities/benchmark.py +
def write(self) -> str:
+    """Writes the benchmark into disk
+
+    Args:
+        filename (str, optional): name of the file. Defaults to config.benchmarks_filename.
+
+    Returns:
+        str: path to the created benchmark file
+    """
+    data = self.todict()
+    bmk_file = os.path.join(self.path, config.benchmarks_filename)
+    if not os.path.exists(bmk_file):
+        os.makedirs(self.path, exist_ok=True)
+    with open(bmk_file, "w") as f:
+        yaml.dump(data, f)
+    return bmk_file
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/entities/cube/index.html b/reference/entities/cube/index.html new file mode 100644 index 000000000..2e5055a3e --- /dev/null +++ b/reference/entities/cube/index.html @@ -0,0 +1,2929 @@ + + + + + + + + + + + + + + + + + + + + Cube - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Cube

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Cube + + +

+ + +
+

+ Bases: Entity, Uploadable, MedperfSchema, DeployableSchema

+ + +

Class representing an MLCube Container

+

Medperf platform uses the MLCube container for components such as +Dataset Preparation, Evaluation, and the Registered Models. MLCube +containers are software containers (e.g., Docker and Singularity) +with standard metadata and a consistent file-system level interface.

+ +
+ Source code in cli/medperf/entities/cube.py +
 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
class Cube(Entity, Uploadable, MedperfSchema, DeployableSchema):
+    """
+    Class representing an MLCube Container
+
+    Medperf platform uses the MLCube container for components such as
+    Dataset Preparation, Evaluation, and the Registered Models. MLCube
+    containers are software containers (e.g., Docker and Singularity)
+    with standard metadata and a consistent file-system level interface.
+    """
+
+    git_mlcube_url: str
+    mlcube_hash: Optional[str]
+    git_parameters_url: Optional[str]
+    parameters_hash: Optional[str]
+    image_tarball_url: Optional[str]
+    image_tarball_hash: Optional[str]
+    image_hash: Optional[str]
+    additional_files_tarball_url: Optional[str] = Field(None, alias="tarball_url")
+    additional_files_tarball_hash: Optional[str] = Field(None, alias="tarball_hash")
+    metadata: dict = {}
+    user_metadata: dict = {}
+
+    def __init__(self, *args, **kwargs):
+        """Creates a Cube instance
+
+        Args:
+            cube_desc (Union[dict, CubeModel]): MLCube Instance description
+        """
+        super().__init__(*args, **kwargs)
+
+        self.generated_uid = self.name
+        path = config.cubes_folder
+        if self.id:
+            path = os.path.join(path, str(self.id))
+        else:
+            path = os.path.join(path, self.generated_uid)
+        # NOTE: maybe have these as @property, to have the same entity reusable
+        #       before and after submission
+        self.path = path
+        self.cube_path = os.path.join(path, config.cube_filename)
+        self.params_path = None
+        if self.git_parameters_url:
+            self.params_path = os.path.join(path, config.params_filename)
+
+    @classmethod
+    def all(cls, local_only: bool = False, filters: dict = {}) -> List["Cube"]:
+        """Class method for retrieving all retrievable MLCubes
+
+        Args:
+            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+        Returns:
+            List[Cube]: List containing all cubes
+        """
+        logging.info("Retrieving all cubes")
+        cubes = []
+        if not local_only:
+            cubes = cls.__remote_all(filters=filters)
+
+        remote_uids = set([cube.id for cube in cubes])
+
+        local_cubes = cls.__local_all()
+
+        cubes += [cube for cube in local_cubes if cube.id not in remote_uids]
+
+        return cubes
+
+    @classmethod
+    def __remote_all(cls, filters: dict) -> List["Cube"]:
+        cubes = []
+
+        try:
+            comms_fn = cls.__remote_prefilter(filters)
+            cubes_meta = comms_fn()
+            cubes = [cls(**meta) for meta in cubes_meta]
+        except CommunicationRetrievalError:
+            msg = "Couldn't retrieve all cubes from the server"
+            logging.warning(msg)
+
+        return cubes
+
+    @classmethod
+    def __remote_prefilter(cls, filters: dict):
+        """Applies filtering logic that must be done before retrieving remote entities
+
+        Args:
+            filters (dict): filters to apply
+
+        Returns:
+            callable: A function for retrieving remote entities with the applied prefilters
+        """
+        comms_fn = config.comms.get_cubes
+        if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+            comms_fn = config.comms.get_user_cubes
+
+        return comms_fn
+
+    @classmethod
+    def __local_all(cls) -> List["Cube"]:
+        cubes = []
+        cubes_folder = config.cubes_folder
+        try:
+            uids = next(os.walk(cubes_folder))[1]
+            logging.debug(f"Local cubes found: {uids}")
+        except StopIteration:
+            msg = "Couldn't iterate over cubes directory"
+            logging.warning(msg)
+            raise MedperfException(msg)
+
+        for uid in uids:
+            meta = cls.__get_local_dict(uid)
+            cube = cls(**meta)
+            cubes.append(cube)
+
+        return cubes
+
+    @classmethod
+    def get(cls, cube_uid: Union[str, int], local_only: bool = False) -> "Cube":
+        """Retrieves and creates a Cube instance from the comms. If cube already exists
+        inside the user's computer then retrieves it from there.
+
+        Args:
+            cube_uid (str): UID of the cube.
+
+        Returns:
+            Cube : a Cube instance with the retrieved data.
+        """
+
+        if not str(cube_uid).isdigit() or local_only:
+            cube = cls.__local_get(cube_uid)
+        else:
+            try:
+                cube = cls.__remote_get(cube_uid)
+            except CommunicationRetrievalError:
+                logging.warning(f"Getting MLCube {cube_uid} from comms failed")
+                logging.info(f"Retrieving MLCube {cube_uid} from local storage")
+                cube = cls.__local_get(cube_uid)
+
+        if not cube.is_valid:
+            raise InvalidEntityError("The requested MLCube is marked as INVALID.")
+        cube.download_config_files()
+        return cube
+
+    @classmethod
+    def __remote_get(cls, cube_uid: int) -> "Cube":
+        logging.debug(f"Retrieving mlcube {cube_uid} remotely")
+        meta = config.comms.get_cube_metadata(cube_uid)
+        cube = cls(**meta)
+        cube.write()
+        return cube
+
+    @classmethod
+    def __local_get(cls, cube_uid: Union[str, int]) -> "Cube":
+        logging.debug(f"Retrieving cube {cube_uid} locally")
+        local_meta = cls.__get_local_dict(cube_uid)
+        cube = cls(**local_meta)
+        return cube
+
+    def download_mlcube(self):
+        url = self.git_mlcube_url
+        path, file_hash = resources.get_cube(url, self.path, self.mlcube_hash)
+        self.cube_path = path
+        self.mlcube_hash = file_hash
+
+    def download_parameters(self):
+        url = self.git_parameters_url
+        if url:
+            path, file_hash = resources.get_cube_params(
+                url, self.path, self.parameters_hash
+            )
+            self.params_path = path
+            self.parameters_hash = file_hash
+
+    def download_additional(self):
+        url = self.additional_files_tarball_url
+        if url:
+            file_hash = resources.get_cube_additional(
+                url, self.path, self.additional_files_tarball_hash
+            )
+            self.additional_files_tarball_hash = file_hash
+
+    def download_image(self):
+        url = self.image_tarball_url
+        tarball_hash = self.image_tarball_hash
+
+        if url:
+            _, local_hash = resources.get_cube_image(url, self.path, tarball_hash)
+            self.image_tarball_hash = local_hash
+        else:
+            if config.platform == "docker":
+                # For docker, image should be pulled before calculating its hash
+                self._get_image_from_registry()
+                self._set_image_hash_from_registry()
+            elif config.platform == "singularity":
+                # For singularity, we need the hash first before trying to convert
+                self._set_image_hash_from_registry()
+
+                image_folder = os.path.join(config.cubes_folder, config.image_path)
+                if os.path.exists(image_folder):
+                    for file in os.listdir(image_folder):
+                        if file == self._converted_singularity_image_name:
+                            return
+                        remove_path(os.path.join(image_folder, file))
+
+                self._get_image_from_registry()
+            else:
+                # TODO: such a check should happen on commands entrypoints, not here
+                raise InvalidArgumentError("Unsupported platform")
+
+    @property
+    def _converted_singularity_image_name(self):
+        return f"{self.image_hash}.sif"
+
+    def _set_image_hash_from_registry(self):
+        # Retrieve image hash from MLCube
+        logging.debug(f"Retrieving {self.id} image hash")
+        tmp_out_yaml = generate_tmp_path()
+        cmd = f"mlcube --log-level {config.loglevel} inspect --mlcube={self.cube_path} --format=yaml"
+        cmd += f" --platform={config.platform} --output-file {tmp_out_yaml}"
+        logging.info(f"Running MLCube command: {cmd}")
+        with spawn_and_kill(cmd, timeout=config.mlcube_inspect_timeout) as proc_wrapper:
+            proc = proc_wrapper.proc
+            combine_proc_sp_text(proc)
+        if proc.exitstatus != 0:
+            raise ExecutionError("There was an error while inspecting the image hash")
+        with open(tmp_out_yaml) as f:
+            mlcube_details = yaml.safe_load(f)
+        remove_path(tmp_out_yaml)
+        local_hash = mlcube_details["hash"]
+        if self.image_hash and local_hash != self.image_hash:
+            raise InvalidEntityError(
+                f"Hash mismatch. Expected {self.image_hash}, found {local_hash}."
+            )
+        self.image_hash = local_hash
+
+    def _get_image_from_registry(self):
+        # Retrieve image from image registry
+        logging.debug(f"Retrieving {self.id} image")
+        cmd = f"mlcube --log-level {config.loglevel} configure --mlcube={self.cube_path} --platform={config.platform}"
+        if config.platform == "singularity":
+            cmd += f" -Psingularity.image={self._converted_singularity_image_name}"
+        logging.info(f"Running MLCube command: {cmd}")
+        with spawn_and_kill(
+            cmd, timeout=config.mlcube_configure_timeout
+        ) as proc_wrapper:
+            proc = proc_wrapper.proc
+            combine_proc_sp_text(proc)
+        if proc.exitstatus != 0:
+            raise ExecutionError("There was an error while retrieving the MLCube image")
+
+    def download_config_files(self):
+        try:
+            self.download_mlcube()
+        except InvalidEntityError as e:
+            raise InvalidEntityError(f"MLCube {self.name} manifest file: {e}")
+
+        try:
+            self.download_parameters()
+        except InvalidEntityError as e:
+            raise InvalidEntityError(f"MLCube {self.name} parameters file: {e}")
+
+    def download_run_files(self):
+        try:
+            self.download_additional()
+        except InvalidEntityError as e:
+            raise InvalidEntityError(f"MLCube {self.name} additional files: {e}")
+
+        try:
+            self.download_image()
+        except InvalidEntityError as e:
+            raise InvalidEntityError(f"MLCube {self.name} image file: {e}")
+
+    def run(
+        self,
+        task: str,
+        output_logs: str = None,
+        string_params: Dict[str, str] = {},
+        timeout: int = None,
+        read_protected_input: bool = True,
+        **kwargs,
+    ):
+        """Executes a given task on the cube instance
+
+        Args:
+            task (str): task to run
+            string_params (Dict[str], optional): Extra parameters that can't be passed as normal function args.
+                                                 Defaults to {}.
+            timeout (int, optional): timeout for the task in seconds. Defaults to None.
+            read_protected_input (bool, optional): Wether to disable write permissions on input volumes. Defaults to True.
+            kwargs (dict): additional arguments that are passed directly to the mlcube command
+        """
+        kwargs.update(string_params)
+        cmd = f"mlcube --log-level {config.loglevel} run"
+        cmd += f" --mlcube={self.cube_path} --task={task} --platform={config.platform} --network=none"
+        if config.gpus is not None:
+            cmd += f" --gpus={config.gpus}"
+        if read_protected_input:
+            cmd += " --mount=ro"
+        for k, v in kwargs.items():
+            cmd_arg = f'{k}="{v}"'
+            cmd = " ".join([cmd, cmd_arg])
+
+        container_loglevel = config.container_loglevel
+
+        # TODO: we should override run args instead of what we are doing below
+        #       we shouldn't allow arbitrary run args unless our client allows it
+        if config.platform == "docker":
+            # use current user
+            cpu_args = self.get_config("docker.cpu_args") or ""
+            gpu_args = self.get_config("docker.gpu_args") or ""
+            cpu_args = " ".join([cpu_args, "-u $(id -u):$(id -g)"]).strip()
+            gpu_args = " ".join([gpu_args, "-u $(id -u):$(id -g)"]).strip()
+            cmd += f' -Pdocker.cpu_args="{cpu_args}"'
+            cmd += f' -Pdocker.gpu_args="{gpu_args}"'
+
+            if container_loglevel:
+                cmd += f' -Pdocker.env_args="-e MEDPERF_LOGLEVEL={container_loglevel.upper()}"'
+        elif config.platform == "singularity":
+            # use -e to discard host env vars, -C to isolate the container (see singularity run --help)
+            run_args = self.get_config("singularity.run_args") or ""
+            run_args = " ".join([run_args, "-eC"]).strip()
+            cmd += f' -Psingularity.run_args="{run_args}"'
+
+            # set image name in case of running docker image with singularity
+            # Assuming we only accept mlcube.yamls with either singularity or docker sections
+            # TODO: make checks on submitted mlcubes
+            singularity_config = self.get_config("singularity")
+            if singularity_config is None:
+                cmd += (
+                    f' -Psingularity.image="{self._converted_singularity_image_name}"'
+                )
+            # TODO: pass logging env for singularity also there
+        else:
+            raise InvalidArgumentError("Unsupported platform")
+
+        # set accelerator count to zero to avoid unexpected behaviours and
+        # force mlcube to only use --gpus to figure out GPU config
+        cmd += " -Pplatform.accelerator_count=0"
+
+        logging.info(f"Running MLCube command: {cmd}")
+        with spawn_and_kill(cmd, timeout=timeout) as proc_wrapper:
+            proc = proc_wrapper.proc
+            proc_out = combine_proc_sp_text(proc)
+
+        if output_logs is not None:
+            with open(output_logs, "w") as f:
+                f.write(proc_out)
+        if proc.exitstatus != 0:
+            raise ExecutionError("There was an error while executing the cube")
+
+        log_storage()
+        return proc
+
+    def get_default_output(self, task: str, out_key: str, param_key: str = None) -> str:
+        """Returns the output parameter specified in the mlcube.yaml file
+
+        Args:
+            task (str): the task of interest
+            out_key (str): key used to identify the desired output in the yaml file
+            param_key (str): key inside the parameters file that completes the output path. Defaults to None.
+
+        Returns:
+            str: the path as specified in the mlcube.yaml file for the desired
+                output for the desired task. Defaults to None if out_key not found
+        """
+        out_path = self.get_config(f"tasks.{task}.parameters.outputs.{out_key}")
+        if out_path is None:
+            return
+
+        if isinstance(out_path, dict):
+            # output is specified as a dict with type and default values
+            out_path = out_path["default"]
+        cube_loc = str(Path(self.cube_path).parent)
+        out_path = os.path.join(cube_loc, "workspace", out_path)
+
+        if self.params_path is not None and param_key is not None:
+            with open(self.params_path, "r") as f:
+                params = yaml.safe_load(f)
+
+            out_path = os.path.join(out_path, params[param_key])
+
+        return out_path
+
+    def get_config(self, identifier):
+        """
+        Returns the output parameter specified in the mlcube.yaml file
+
+        Args:
+            identifier (str): `.` separated keys to traverse the mlcube dict
+        Returns:
+            str: the parameter value, None if not found
+        """
+        with open(self.cube_path, "r") as f:
+            cube = yaml.safe_load(f)
+
+        keys = identifier.split(".")
+        for key in keys:
+            if key not in cube:
+                return
+            cube = cube[key]
+
+        return cube
+
+    def todict(self) -> Dict:
+        return self.extended_dict()
+
+    def write(self):
+        cube_loc = str(Path(self.cube_path).parent)
+        meta_file = os.path.join(cube_loc, config.cube_metadata_filename)
+        os.makedirs(cube_loc, exist_ok=True)
+        with open(meta_file, "w") as f:
+            yaml.dump(self.todict(), f)
+        return meta_file
+
+    def upload(self):
+        if self.for_test:
+            raise InvalidArgumentError("Cannot upload test mlcubes.")
+        cube_dict = self.todict()
+        updated_cube_dict = config.comms.upload_mlcube(cube_dict)
+        return updated_cube_dict
+
+    @classmethod
+    def __get_local_dict(cls, uid):
+        cubes_folder = config.cubes_folder
+        meta_file = os.path.join(cubes_folder, str(uid), config.cube_metadata_filename)
+        if not os.path.exists(meta_file):
+            raise InvalidArgumentError(
+                "The requested mlcube information could not be found locally"
+            )
+        with open(meta_file, "r") as f:
+            meta = yaml.safe_load(f)
+        return meta
+
+    def display_dict(self):
+        return {
+            "UID": self.identifier,
+            "Name": self.name,
+            "Config File": self.git_mlcube_url,
+            "State": self.state,
+            "Created At": self.created_at,
+            "Registered": self.is_registered,
+        }
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__init__(*args, **kwargs) + +

+ + +
+ +

Creates a Cube instance

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
cube_desc + Union[dict, CubeModel] + +
+

MLCube Instance description

+
+
+ required +
+ +
+ Source code in cli/medperf/entities/cube.py +
51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
def __init__(self, *args, **kwargs):
+    """Creates a Cube instance
+
+    Args:
+        cube_desc (Union[dict, CubeModel]): MLCube Instance description
+    """
+    super().__init__(*args, **kwargs)
+
+    self.generated_uid = self.name
+    path = config.cubes_folder
+    if self.id:
+        path = os.path.join(path, str(self.id))
+    else:
+        path = os.path.join(path, self.generated_uid)
+    # NOTE: maybe have these as @property, to have the same entity reusable
+    #       before and after submission
+    self.path = path
+    self.cube_path = os.path.join(path, config.cube_filename)
+    self.params_path = None
+    if self.git_parameters_url:
+        self.params_path = os.path.join(path, config.params_filename)
+
+
+
+ +
+ + +
+ + + +

+__remote_prefilter(filters) + + + classmethod + + +

+ + +
+ +

Applies filtering logic that must be done before retrieving remote entities

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filters + dict + +
+

filters to apply

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
callable + +
+

A function for retrieving remote entities with the applied prefilters

+
+
+ +
+ Source code in cli/medperf/entities/cube.py +
@classmethod
+def __remote_prefilter(cls, filters: dict):
+    """Applies filtering logic that must be done before retrieving remote entities
+
+    Args:
+        filters (dict): filters to apply
+
+    Returns:
+        callable: A function for retrieving remote entities with the applied prefilters
+    """
+    comms_fn = config.comms.get_cubes
+    if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+        comms_fn = config.comms.get_user_cubes
+
+    return comms_fn
+
+
+
+ +
+ + +
+ + + +

+all(local_only=False, filters={}) + + + classmethod + + +

+ + +
+ +

Class method for retrieving all retrievable MLCubes

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
local_only + bool + +
+

Wether to retrieve only local entities. Defaults to False.

+
+
+ False +
filters + dict + +
+

key-value pairs specifying filters to apply to the list of entities.

+
+
+ {} +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[Cube] + +
+

List[Cube]: List containing all cubes

+
+
+ +
+ Source code in cli/medperf/entities/cube.py +
73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
@classmethod
+def all(cls, local_only: bool = False, filters: dict = {}) -> List["Cube"]:
+    """Class method for retrieving all retrievable MLCubes
+
+    Args:
+        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+    Returns:
+        List[Cube]: List containing all cubes
+    """
+    logging.info("Retrieving all cubes")
+    cubes = []
+    if not local_only:
+        cubes = cls.__remote_all(filters=filters)
+
+    remote_uids = set([cube.id for cube in cubes])
+
+    local_cubes = cls.__local_all()
+
+    cubes += [cube for cube in local_cubes if cube.id not in remote_uids]
+
+    return cubes
+
+
+
+ +
+ + +
+ + + +

+get(cube_uid, local_only=False) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Cube instance from the comms. If cube already exists +inside the user's computer then retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
cube_uid + str + +
+

UID of the cube.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Cube + Cube + +
+

a Cube instance with the retrieved data.

+
+
+ +
+ Source code in cli/medperf/entities/cube.py +
@classmethod
+def get(cls, cube_uid: Union[str, int], local_only: bool = False) -> "Cube":
+    """Retrieves and creates a Cube instance from the comms. If cube already exists
+    inside the user's computer then retrieves it from there.
+
+    Args:
+        cube_uid (str): UID of the cube.
+
+    Returns:
+        Cube : a Cube instance with the retrieved data.
+    """
+
+    if not str(cube_uid).isdigit() or local_only:
+        cube = cls.__local_get(cube_uid)
+    else:
+        try:
+            cube = cls.__remote_get(cube_uid)
+        except CommunicationRetrievalError:
+            logging.warning(f"Getting MLCube {cube_uid} from comms failed")
+            logging.info(f"Retrieving MLCube {cube_uid} from local storage")
+            cube = cls.__local_get(cube_uid)
+
+    if not cube.is_valid:
+        raise InvalidEntityError("The requested MLCube is marked as INVALID.")
+    cube.download_config_files()
+    return cube
+
+
+
+ +
+ + +
+ + + +

+get_config(identifier) + +

+ + +
+ +

Returns the output parameter specified in the mlcube.yaml file

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
identifier + str + +
+

. separated keys to traverse the mlcube dict

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + +
+

the parameter value, None if not found

+
+
+ +
+ Source code in cli/medperf/entities/cube.py +
def get_config(self, identifier):
+    """
+    Returns the output parameter specified in the mlcube.yaml file
+
+    Args:
+        identifier (str): `.` separated keys to traverse the mlcube dict
+    Returns:
+        str: the parameter value, None if not found
+    """
+    with open(self.cube_path, "r") as f:
+        cube = yaml.safe_load(f)
+
+    keys = identifier.split(".")
+    for key in keys:
+        if key not in cube:
+            return
+        cube = cube[key]
+
+    return cube
+
+
+
+ +
+ + +
+ + + +

+get_default_output(task, out_key, param_key=None) + +

+ + +
+ +

Returns the output parameter specified in the mlcube.yaml file

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
task + str + +
+

the task of interest

+
+
+ required +
out_key + str + +
+

key used to identify the desired output in the yaml file

+
+
+ required +
param_key + str + +
+

key inside the parameters file that completes the output path. Defaults to None.

+
+
+ None +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

the path as specified in the mlcube.yaml file for the desired +output for the desired task. Defaults to None if out_key not found

+
+
+ +
+ Source code in cli/medperf/entities/cube.py +
def get_default_output(self, task: str, out_key: str, param_key: str = None) -> str:
+    """Returns the output parameter specified in the mlcube.yaml file
+
+    Args:
+        task (str): the task of interest
+        out_key (str): key used to identify the desired output in the yaml file
+        param_key (str): key inside the parameters file that completes the output path. Defaults to None.
+
+    Returns:
+        str: the path as specified in the mlcube.yaml file for the desired
+            output for the desired task. Defaults to None if out_key not found
+    """
+    out_path = self.get_config(f"tasks.{task}.parameters.outputs.{out_key}")
+    if out_path is None:
+        return
+
+    if isinstance(out_path, dict):
+        # output is specified as a dict with type and default values
+        out_path = out_path["default"]
+    cube_loc = str(Path(self.cube_path).parent)
+    out_path = os.path.join(cube_loc, "workspace", out_path)
+
+    if self.params_path is not None and param_key is not None:
+        with open(self.params_path, "r") as f:
+            params = yaml.safe_load(f)
+
+        out_path = os.path.join(out_path, params[param_key])
+
+    return out_path
+
+
+
+ +
+ + +
+ + + +

+run(task, output_logs=None, string_params={}, timeout=None, read_protected_input=True, **kwargs) + +

+ + +
+ +

Executes a given task on the cube instance

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
task + str + +
+

task to run

+
+
+ required +
string_params + Dict[str] + +
+

Extra parameters that can't be passed as normal function args. + Defaults to {}.

+
+
+ {} +
timeout + int + +
+

timeout for the task in seconds. Defaults to None.

+
+
+ None +
read_protected_input + bool + +
+

Wether to disable write permissions on input volumes. Defaults to True.

+
+
+ True +
kwargs + dict + +
+

additional arguments that are passed directly to the mlcube command

+
+
+ {} +
+ +
+ Source code in cli/medperf/entities/cube.py +
def run(
+    self,
+    task: str,
+    output_logs: str = None,
+    string_params: Dict[str, str] = {},
+    timeout: int = None,
+    read_protected_input: bool = True,
+    **kwargs,
+):
+    """Executes a given task on the cube instance
+
+    Args:
+        task (str): task to run
+        string_params (Dict[str], optional): Extra parameters that can't be passed as normal function args.
+                                             Defaults to {}.
+        timeout (int, optional): timeout for the task in seconds. Defaults to None.
+        read_protected_input (bool, optional): Wether to disable write permissions on input volumes. Defaults to True.
+        kwargs (dict): additional arguments that are passed directly to the mlcube command
+    """
+    kwargs.update(string_params)
+    cmd = f"mlcube --log-level {config.loglevel} run"
+    cmd += f" --mlcube={self.cube_path} --task={task} --platform={config.platform} --network=none"
+    if config.gpus is not None:
+        cmd += f" --gpus={config.gpus}"
+    if read_protected_input:
+        cmd += " --mount=ro"
+    for k, v in kwargs.items():
+        cmd_arg = f'{k}="{v}"'
+        cmd = " ".join([cmd, cmd_arg])
+
+    container_loglevel = config.container_loglevel
+
+    # TODO: we should override run args instead of what we are doing below
+    #       we shouldn't allow arbitrary run args unless our client allows it
+    if config.platform == "docker":
+        # use current user
+        cpu_args = self.get_config("docker.cpu_args") or ""
+        gpu_args = self.get_config("docker.gpu_args") or ""
+        cpu_args = " ".join([cpu_args, "-u $(id -u):$(id -g)"]).strip()
+        gpu_args = " ".join([gpu_args, "-u $(id -u):$(id -g)"]).strip()
+        cmd += f' -Pdocker.cpu_args="{cpu_args}"'
+        cmd += f' -Pdocker.gpu_args="{gpu_args}"'
+
+        if container_loglevel:
+            cmd += f' -Pdocker.env_args="-e MEDPERF_LOGLEVEL={container_loglevel.upper()}"'
+    elif config.platform == "singularity":
+        # use -e to discard host env vars, -C to isolate the container (see singularity run --help)
+        run_args = self.get_config("singularity.run_args") or ""
+        run_args = " ".join([run_args, "-eC"]).strip()
+        cmd += f' -Psingularity.run_args="{run_args}"'
+
+        # set image name in case of running docker image with singularity
+        # Assuming we only accept mlcube.yamls with either singularity or docker sections
+        # TODO: make checks on submitted mlcubes
+        singularity_config = self.get_config("singularity")
+        if singularity_config is None:
+            cmd += (
+                f' -Psingularity.image="{self._converted_singularity_image_name}"'
+            )
+        # TODO: pass logging env for singularity also there
+    else:
+        raise InvalidArgumentError("Unsupported platform")
+
+    # set accelerator count to zero to avoid unexpected behaviours and
+    # force mlcube to only use --gpus to figure out GPU config
+    cmd += " -Pplatform.accelerator_count=0"
+
+    logging.info(f"Running MLCube command: {cmd}")
+    with spawn_and_kill(cmd, timeout=timeout) as proc_wrapper:
+        proc = proc_wrapper.proc
+        proc_out = combine_proc_sp_text(proc)
+
+    if output_logs is not None:
+        with open(output_logs, "w") as f:
+            f.write(proc_out)
+    if proc.exitstatus != 0:
+        raise ExecutionError("There was an error while executing the cube")
+
+    log_storage()
+    return proc
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/entities/dataset/index.html b/reference/entities/dataset/index.html new file mode 100644 index 000000000..4a192246f --- /dev/null +++ b/reference/entities/dataset/index.html @@ -0,0 +1,2204 @@ + + + + + + + + + + + + + + + + + + + + Dataset - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Dataset

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Dataset + + +

+ + +
+

+ Bases: Entity, Uploadable, MedperfSchema, DeployableSchema

+ + +

Class representing a Dataset

+

Datasets are stored locally in the Data Owner's machine. They contain +information regarding the prepared dataset, such as name and description, +general statistics and an UID generated by hashing the contents of the +data preparation output.

+ +
+ Source code in cli/medperf/entities/dataset.py +
class Dataset(Entity, Uploadable, MedperfSchema, DeployableSchema):
+    """
+    Class representing a Dataset
+
+    Datasets are stored locally in the Data Owner's machine. They contain
+    information regarding the prepared dataset, such as name and description,
+    general statistics and an UID generated by hashing the contents of the
+    data preparation output.
+    """
+
+    description: Optional[str] = Field(None, max_length=20)
+    location: Optional[str] = Field(None, max_length=20)
+    input_data_hash: str
+    generated_uid: str
+    data_preparation_mlcube: Union[int, str]
+    split_seed: Optional[int]
+    generated_metadata: dict = Field(..., alias="metadata")
+    user_metadata: dict = {}
+    report: dict = {}
+    submitted_as_prepared: bool
+
+    @validator("data_preparation_mlcube", pre=True, always=True)
+    def check_data_preparation_mlcube(cls, v, *, values, **kwargs):
+        if not isinstance(v, int) and not values["for_test"]:
+            raise ValueError(
+                "data_preparation_mlcube must be an integer if not running a compatibility test"
+            )
+        return v
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        path = config.datasets_folder
+        if self.id:
+            path = os.path.join(path, str(self.id))
+        else:
+            path = os.path.join(path, self.generated_uid)
+
+        self.path = path
+        self.data_path = os.path.join(self.path, "data")
+        self.labels_path = os.path.join(self.path, "labels")
+        self.report_path = os.path.join(self.path, config.report_file)
+        self.metadata_path = os.path.join(self.path, config.metadata_folder)
+        self.statistics_path = os.path.join(self.path, config.statistics_filename)
+
+    def set_raw_paths(self, raw_data_path: str, raw_labels_path: str):
+        raw_paths_file = os.path.join(self.path, config.dataset_raw_paths_file)
+        data = {"data_path": raw_data_path, "labels_path": raw_labels_path}
+        with open(raw_paths_file, "w") as f:
+            yaml.dump(data, f)
+
+    def get_raw_paths(self):
+        raw_paths_file = os.path.join(self.path, config.dataset_raw_paths_file)
+        with open(raw_paths_file) as f:
+            data = yaml.safe_load(f)
+        return data["data_path"], data["labels_path"]
+
+    def mark_as_ready(self):
+        flag_file = os.path.join(self.path, config.ready_flag_file)
+        with open(flag_file, "w"):
+            pass
+
+    def unmark_as_ready(self):
+        flag_file = os.path.join(self.path, config.ready_flag_file)
+        remove_path(flag_file)
+
+    def is_ready(self):
+        flag_file = os.path.join(self.path, config.ready_flag_file)
+        return os.path.exists(flag_file)
+
+    def todict(self):
+        return self.extended_dict()
+
+    @classmethod
+    def all(cls, local_only: bool = False, filters: dict = {}) -> List["Dataset"]:
+        """Gets and creates instances of all the locally prepared datasets
+
+        Args:
+            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+        Returns:
+            List[Dataset]: a list of Dataset instances.
+        """
+        logging.info("Retrieving all datasets")
+        dsets = []
+        if not local_only:
+            dsets = cls.__remote_all(filters=filters)
+
+        remote_uids = set([dset.id for dset in dsets])
+
+        local_dsets = cls.__local_all()
+
+        dsets += [dset for dset in local_dsets if dset.id not in remote_uids]
+
+        return dsets
+
+    @classmethod
+    def __remote_all(cls, filters: dict) -> List["Dataset"]:
+        dsets = []
+        try:
+            comms_fn = cls.__remote_prefilter(filters)
+            dsets_meta = comms_fn()
+            dsets = [cls(**meta) for meta in dsets_meta]
+        except CommunicationRetrievalError:
+            msg = "Couldn't retrieve all datasets from the server"
+            logging.warning(msg)
+
+        return dsets
+
+    @classmethod
+    def __remote_prefilter(cls, filters: dict) -> callable:
+        """Applies filtering logic that must be done before retrieving remote entities
+
+        Args:
+            filters (dict): filters to apply
+
+        Returns:
+            callable: A function for retrieving remote entities with the applied prefilters
+        """
+        comms_fn = config.comms.get_datasets
+        if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+            comms_fn = config.comms.get_user_datasets
+
+        if "mlcube" in filters and filters["mlcube"] is not None:
+
+            def func():
+                return config.comms.get_mlcube_datasets(filters["mlcube"])
+
+            comms_fn = func
+
+        return comms_fn
+
+    @classmethod
+    def __local_all(cls) -> List["Dataset"]:
+        dsets = []
+        datasets_folder = config.datasets_folder
+        try:
+            uids = next(os.walk(datasets_folder))[1]
+        except StopIteration:
+            msg = "Couldn't iterate over the dataset directory"
+            logging.warning(msg)
+            raise MedperfException(msg)
+
+        for uid in uids:
+            local_meta = cls.__get_local_dict(uid)
+            dset = cls(**local_meta)
+            dsets.append(dset)
+
+        return dsets
+
+    @classmethod
+    def get(cls, dset_uid: Union[str, int], local_only: bool = False) -> "Dataset":
+        """Retrieves and creates a Dataset instance from the comms instance.
+        If the dataset is present in the user's machine then it retrieves it from there.
+
+        Args:
+            dset_uid (str): server UID of the dataset
+
+        Returns:
+            Dataset: Specified Dataset Instance
+        """
+        if not str(dset_uid).isdigit() or local_only:
+            return cls.__local_get(dset_uid)
+
+        try:
+            return cls.__remote_get(dset_uid)
+        except CommunicationRetrievalError:
+            logging.warning(f"Getting Dataset {dset_uid} from comms failed")
+            logging.info(f"Looking for dataset {dset_uid} locally")
+            return cls.__local_get(dset_uid)
+
+    @classmethod
+    def __remote_get(cls, dset_uid: int) -> "Dataset":
+        """Retrieves and creates a Dataset instance from the comms instance.
+        If the dataset is present in the user's machine then it retrieves it from there.
+
+        Args:
+            dset_uid (str): server UID of the dataset
+
+        Returns:
+            Dataset: Specified Dataset Instance
+        """
+        logging.debug(f"Retrieving dataset {dset_uid} remotely")
+        meta = config.comms.get_dataset(dset_uid)
+        dataset = cls(**meta)
+        dataset.write()
+        return dataset
+
+    @classmethod
+    def __local_get(cls, dset_uid: Union[str, int]) -> "Dataset":
+        """Retrieves and creates a Dataset instance from the comms instance.
+        If the dataset is present in the user's machine then it retrieves it from there.
+
+        Args:
+            dset_uid (str): server UID of the dataset
+
+        Returns:
+            Dataset: Specified Dataset Instance
+        """
+        logging.debug(f"Retrieving dataset {dset_uid} locally")
+        local_meta = cls.__get_local_dict(dset_uid)
+        dataset = cls(**local_meta)
+        return dataset
+
+    def write(self):
+        logging.info(f"Updating registration information for dataset: {self.id}")
+        logging.debug(f"registration information: {self.todict()}")
+        regfile = os.path.join(self.path, config.reg_file)
+        os.makedirs(self.path, exist_ok=True)
+        with open(regfile, "w") as f:
+            yaml.dump(self.todict(), f)
+        return regfile
+
+    def upload(self):
+        """Uploads the registration information to the comms.
+
+        Args:
+            comms (Comms): Instance of the comms interface.
+        """
+        if self.for_test:
+            raise InvalidArgumentError("Cannot upload test datasets.")
+        dataset_dict = self.todict()
+        updated_dataset_dict = config.comms.upload_dataset(dataset_dict)
+        return updated_dataset_dict
+
+    @classmethod
+    def __get_local_dict(cls, data_uid):
+        dataset_path = os.path.join(config.datasets_folder, str(data_uid))
+        regfile = os.path.join(dataset_path, config.reg_file)
+        if not os.path.exists(regfile):
+            raise InvalidArgumentError(
+                "The requested dataset information could not be found locally"
+            )
+        with open(regfile, "r") as f:
+            reg = yaml.safe_load(f)
+        return reg
+
+    def display_dict(self):
+        return {
+            "UID": self.identifier,
+            "Name": self.name,
+            "Description": self.description,
+            "Location": self.location,
+            "Data Preparation Cube UID": self.data_preparation_mlcube,
+            "Generated Hash": self.generated_uid,
+            "State": self.state,
+            "Created At": self.created_at,
+            "Registered": self.is_registered,
+            "Submitted as Prepared": self.submitted_as_prepared,
+            "Status": "\n".join([f"{k}: {v}" for k, v in self.report.items()]),
+            "Owner": self.owner,
+        }
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__local_get(dset_uid) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Dataset instance from the comms instance. +If the dataset is present in the user's machine then it retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dset_uid + str + +
+

server UID of the dataset

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dataset + Dataset + +
+

Specified Dataset Instance

+
+
+ +
+ Source code in cli/medperf/entities/dataset.py +
@classmethod
+def __local_get(cls, dset_uid: Union[str, int]) -> "Dataset":
+    """Retrieves and creates a Dataset instance from the comms instance.
+    If the dataset is present in the user's machine then it retrieves it from there.
+
+    Args:
+        dset_uid (str): server UID of the dataset
+
+    Returns:
+        Dataset: Specified Dataset Instance
+    """
+    logging.debug(f"Retrieving dataset {dset_uid} locally")
+    local_meta = cls.__get_local_dict(dset_uid)
+    dataset = cls(**local_meta)
+    return dataset
+
+
+
+ +
+ + +
+ + + +

+__remote_get(dset_uid) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Dataset instance from the comms instance. +If the dataset is present in the user's machine then it retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dset_uid + str + +
+

server UID of the dataset

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dataset + Dataset + +
+

Specified Dataset Instance

+
+
+ +
+ Source code in cli/medperf/entities/dataset.py +
@classmethod
+def __remote_get(cls, dset_uid: int) -> "Dataset":
+    """Retrieves and creates a Dataset instance from the comms instance.
+    If the dataset is present in the user's machine then it retrieves it from there.
+
+    Args:
+        dset_uid (str): server UID of the dataset
+
+    Returns:
+        Dataset: Specified Dataset Instance
+    """
+    logging.debug(f"Retrieving dataset {dset_uid} remotely")
+    meta = config.comms.get_dataset(dset_uid)
+    dataset = cls(**meta)
+    dataset.write()
+    return dataset
+
+
+
+ +
+ + +
+ + + +

+__remote_prefilter(filters) + + + classmethod + + +

+ + +
+ +

Applies filtering logic that must be done before retrieving remote entities

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filters + dict + +
+

filters to apply

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
callable + callable + +
+

A function for retrieving remote entities with the applied prefilters

+
+
+ +
+ Source code in cli/medperf/entities/dataset.py +
@classmethod
+def __remote_prefilter(cls, filters: dict) -> callable:
+    """Applies filtering logic that must be done before retrieving remote entities
+
+    Args:
+        filters (dict): filters to apply
+
+    Returns:
+        callable: A function for retrieving remote entities with the applied prefilters
+    """
+    comms_fn = config.comms.get_datasets
+    if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+        comms_fn = config.comms.get_user_datasets
+
+    if "mlcube" in filters and filters["mlcube"] is not None:
+
+        def func():
+            return config.comms.get_mlcube_datasets(filters["mlcube"])
+
+        comms_fn = func
+
+    return comms_fn
+
+
+
+ +
+ + +
+ + + +

+all(local_only=False, filters={}) + + + classmethod + + +

+ + +
+ +

Gets and creates instances of all the locally prepared datasets

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
local_only + bool + +
+

Wether to retrieve only local entities. Defaults to False.

+
+
+ False +
filters + dict + +
+

key-value pairs specifying filters to apply to the list of entities.

+
+
+ {} +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[Dataset] + +
+

List[Dataset]: a list of Dataset instances.

+
+
+ +
+ Source code in cli/medperf/entities/dataset.py +
@classmethod
+def all(cls, local_only: bool = False, filters: dict = {}) -> List["Dataset"]:
+    """Gets and creates instances of all the locally prepared datasets
+
+    Args:
+        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+    Returns:
+        List[Dataset]: a list of Dataset instances.
+    """
+    logging.info("Retrieving all datasets")
+    dsets = []
+    if not local_only:
+        dsets = cls.__remote_all(filters=filters)
+
+    remote_uids = set([dset.id for dset in dsets])
+
+    local_dsets = cls.__local_all()
+
+    dsets += [dset for dset in local_dsets if dset.id not in remote_uids]
+
+    return dsets
+
+
+
+ +
+ + +
+ + + +

+get(dset_uid, local_only=False) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Dataset instance from the comms instance. +If the dataset is present in the user's machine then it retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
dset_uid + str + +
+

server UID of the dataset

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dataset + Dataset + +
+

Specified Dataset Instance

+
+
+ +
+ Source code in cli/medperf/entities/dataset.py +
@classmethod
+def get(cls, dset_uid: Union[str, int], local_only: bool = False) -> "Dataset":
+    """Retrieves and creates a Dataset instance from the comms instance.
+    If the dataset is present in the user's machine then it retrieves it from there.
+
+    Args:
+        dset_uid (str): server UID of the dataset
+
+    Returns:
+        Dataset: Specified Dataset Instance
+    """
+    if not str(dset_uid).isdigit() or local_only:
+        return cls.__local_get(dset_uid)
+
+    try:
+        return cls.__remote_get(dset_uid)
+    except CommunicationRetrievalError:
+        logging.warning(f"Getting Dataset {dset_uid} from comms failed")
+        logging.info(f"Looking for dataset {dset_uid} locally")
+        return cls.__local_get(dset_uid)
+
+
+
+ +
+ + +
+ + + +

+upload() + +

+ + +
+ +

Uploads the registration information to the comms.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
comms + Comms + +
+

Instance of the comms interface.

+
+
+ required +
+ +
+ Source code in cli/medperf/entities/dataset.py +
def upload(self):
+    """Uploads the registration information to the comms.
+
+    Args:
+        comms (Comms): Instance of the comms interface.
+    """
+    if self.for_test:
+        raise InvalidArgumentError("Cannot upload test datasets.")
+    dataset_dict = self.todict()
+    updated_dataset_dict = config.comms.upload_dataset(dataset_dict)
+    return updated_dataset_dict
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/entities/interface/index.html b/reference/entities/interface/index.html new file mode 100644 index 000000000..f43b3fc47 --- /dev/null +++ b/reference/entities/interface/index.html @@ -0,0 +1,1696 @@ + + + + + + + + + + + + + + + + + + + + Interface - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Interface

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Entity + + +

+ + +
+

+ Bases: ABC

+ + +
+ Source code in cli/medperf/entities/interface.py +
 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
class Entity(ABC):
+    @abstractmethod
+    def all(
+        cls, local_only: bool = False, comms_func: callable = None
+    ) -> List["Entity"]:
+        """Gets a list of all instances of the respective entity.
+        Wether the list is local or remote depends on the implementation.
+
+        Args:
+            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+            comms_func (callable, optional): Function to use to retrieve remote entities.
+                If not provided, will use the default entrypoint.
+
+        Returns:
+            List[Entity]: a list of entities.
+        """
+
+    @abstractmethod
+    def get(cls, uid: Union[str, int]) -> "Entity":
+        """Gets an instance of the respective entity.
+        Wether this requires only local read or remote calls depends
+        on the implementation.
+
+        Args:
+            uid (str): Unique Identifier to retrieve the entity
+
+        Returns:
+            Entity: Entity Instance associated to the UID
+        """
+
+    @abstractmethod
+    def todict(self) -> Dict:
+        """Dictionary representation of the entity
+
+        Returns:
+            Dict: Dictionary containing information about the entity
+        """
+
+    @abstractmethod
+    def write(self) -> str:
+        """Writes the entity to the local storage
+
+        Returns:
+            str: Path to the stored entity
+        """
+
+    @abstractmethod
+    def display_dict(self) -> dict:
+        """Returns a dictionary of entity properties that can be displayed
+        to a user interface using a verbose name of the property rather than
+        the internal names
+
+        Returns:
+            dict: the display dictionary
+        """
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+all(local_only=False, comms_func=None) + + + abstractmethod + + +

+ + +
+ +

Gets a list of all instances of the respective entity. +Wether the list is local or remote depends on the implementation.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
local_only + bool + +
+

Wether to retrieve only local entities. Defaults to False.

+
+
+ False +
comms_func + callable + +
+

Function to use to retrieve remote entities. +If not provided, will use the default entrypoint.

+
+
+ None +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[Entity] + +
+

List[Entity]: a list of entities.

+
+
+ +
+ Source code in cli/medperf/entities/interface.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
@abstractmethod
+def all(
+    cls, local_only: bool = False, comms_func: callable = None
+) -> List["Entity"]:
+    """Gets a list of all instances of the respective entity.
+    Wether the list is local or remote depends on the implementation.
+
+    Args:
+        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+        comms_func (callable, optional): Function to use to retrieve remote entities.
+            If not provided, will use the default entrypoint.
+
+    Returns:
+        List[Entity]: a list of entities.
+    """
+
+
+
+ +
+ + +
+ + + +

+display_dict() + + + abstractmethod + + +

+ + +
+ +

Returns a dictionary of entity properties that can be displayed +to a user interface using a verbose name of the property rather than +the internal names

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

the display dictionary

+
+
+ +
+ Source code in cli/medperf/entities/interface.py +
51
+52
+53
+54
+55
+56
+57
+58
+59
@abstractmethod
+def display_dict(self) -> dict:
+    """Returns a dictionary of entity properties that can be displayed
+    to a user interface using a verbose name of the property rather than
+    the internal names
+
+    Returns:
+        dict: the display dictionary
+    """
+
+
+
+ +
+ + +
+ + + +

+get(uid) + + + abstractmethod + + +

+ + +
+ +

Gets an instance of the respective entity. +Wether this requires only local read or remote calls depends +on the implementation.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
uid + str + +
+

Unique Identifier to retrieve the entity

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Entity + Entity + +
+

Entity Instance associated to the UID

+
+
+ +
+ Source code in cli/medperf/entities/interface.py +
22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
@abstractmethod
+def get(cls, uid: Union[str, int]) -> "Entity":
+    """Gets an instance of the respective entity.
+    Wether this requires only local read or remote calls depends
+    on the implementation.
+
+    Args:
+        uid (str): Unique Identifier to retrieve the entity
+
+    Returns:
+        Entity: Entity Instance associated to the UID
+    """
+
+
+
+ +
+ + +
+ + + +

+todict() + + + abstractmethod + + +

+ + +
+ +

Dictionary representation of the entity

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dict + Dict + +
+

Dictionary containing information about the entity

+
+
+ +
+ Source code in cli/medperf/entities/interface.py +
35
+36
+37
+38
+39
+40
+41
@abstractmethod
+def todict(self) -> Dict:
+    """Dictionary representation of the entity
+
+    Returns:
+        Dict: Dictionary containing information about the entity
+    """
+
+
+
+ +
+ + +
+ + + +

+write() + + + abstractmethod + + +

+ + +
+ +

Writes the entity to the local storage

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

Path to the stored entity

+
+
+ +
+ Source code in cli/medperf/entities/interface.py +
43
+44
+45
+46
+47
+48
+49
@abstractmethod
+def write(self) -> str:
+    """Writes the entity to the local storage
+
+    Returns:
+        str: Path to the stored entity
+    """
+
+
+
+ +
+ + + +
+ +
+ +
+ +
+ + + +

+ Uploadable + + +

+ + +
+ + +
+ Source code in cli/medperf/entities/interface.py +
62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
class Uploadable:
+    @abstractmethod
+    def upload(self) -> Dict:
+        """Upload the entity-related information to the communication's interface
+
+        Returns:
+            Dict: Dictionary with the updated entity information
+        """
+
+    @property
+    def identifier(self):
+        return self.id or self.generated_uid
+
+    @property
+    def is_registered(self):
+        return self.id is not None
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+upload() + + + abstractmethod + + +

+ + +
+ +

Upload the entity-related information to the communication's interface

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dict + Dict + +
+

Dictionary with the updated entity information

+
+
+ +
+ Source code in cli/medperf/entities/interface.py +
63
+64
+65
+66
+67
+68
+69
@abstractmethod
+def upload(self) -> Dict:
+    """Upload the entity-related information to the communication's interface
+
+    Returns:
+        Dict: Dictionary with the updated entity information
+    """
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/entities/report/index.html b/reference/entities/report/index.html new file mode 100644 index 000000000..71ac8a6dd --- /dev/null +++ b/reference/entities/report/index.html @@ -0,0 +1,1508 @@ + + + + + + + + + + + + + + + + + + + + Report - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Report

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ TestReport + + +

+ + +
+

+ Bases: Entity, MedperfBaseSchema

+ + +

Class representing a compatibility test report entry

+

A test report consists of the components of a test execution: +- data used, which can be: + - a demo dataset url and its hash, or + - a raw data path and its labels path, or + - a prepared dataset uid +- Data preparation cube if the data used was not already prepared +- model cube +- evaluator cube +- results

+ +
+ Source code in cli/medperf/entities/report.py +
class TestReport(Entity, MedperfBaseSchema):
+    """
+    Class representing a compatibility test report entry
+
+    A test report consists of the components of a test execution:
+    - data used, which can be:
+        - a demo dataset url and its hash, or
+        - a raw data path and its labels path, or
+        - a prepared dataset uid
+    - Data preparation cube if the data used was not already prepared
+    - model cube
+    - evaluator cube
+    - results
+    """
+
+    demo_dataset_url: Optional[str]
+    demo_dataset_hash: Optional[str]
+    data_path: Optional[str]
+    labels_path: Optional[str]
+    prepared_data_hash: Optional[str]
+    data_preparation_mlcube: Optional[Union[int, str]]
+    model: Union[int, str]
+    data_evaluator_mlcube: Union[int, str]
+    results: Optional[dict]
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.generated_uid = self.__generate_uid()
+        path = config.tests_folder
+        self.path = os.path.join(path, self.generated_uid)
+
+    def __generate_uid(self):
+        """A helper that generates a unique hash for a test report."""
+        params = self.todict()
+        del params["results"]
+        params = str(params)
+        return hashlib.sha256(params.encode()).hexdigest()
+
+    def set_results(self, results):
+        self.results = results
+
+    @classmethod
+    def all(
+        cls, local_only: bool = False, mine_only: bool = False
+    ) -> List["TestReport"]:
+        """Gets and creates instances of test reports.
+        Arguments are only specified for compatibility with
+        `Entity.List` and `Entity.View`, but they don't contribute to
+        the logic.
+
+        Returns:
+            List[TestReport]: List containing all test reports
+        """
+        logging.info("Retrieving all reports")
+        reports = []
+        tests_folder = config.tests_folder
+        try:
+            uids = next(os.walk(tests_folder))[1]
+        except StopIteration:
+            msg = "Couldn't iterate over the tests directory"
+            logging.warning(msg)
+            raise RuntimeError(msg)
+
+        for uid in uids:
+            local_meta = cls.__get_local_dict(uid)
+            report = cls(**local_meta)
+            reports.append(report)
+
+        return reports
+
+    @classmethod
+    def get(cls, report_uid: str) -> "TestReport":
+        """Retrieves and creates a TestReport instance obtained the user's machine
+
+        Args:
+            report_uid (str): UID of the TestReport instance
+
+        Returns:
+            TestReport: Specified TestReport instance
+        """
+        logging.debug(f"Retrieving report {report_uid}")
+        report_dict = cls.__get_local_dict(report_uid)
+        report = cls(**report_dict)
+        report.write()
+        return report
+
+    def todict(self):
+        return self.extended_dict()
+
+    def write(self):
+        report_file = os.path.join(self.path, config.test_report_file)
+        os.makedirs(self.path, exist_ok=True)
+        with open(report_file, "w") as f:
+            yaml.dump(self.todict(), f)
+        return report_file
+
+    @classmethod
+    def __get_local_dict(cls, local_uid):
+        report_path = os.path.join(config.tests_folder, str(local_uid))
+        report_file = os.path.join(report_path, config.test_report_file)
+        if not os.path.exists(report_file):
+            raise InvalidArgumentError(
+                f"The requested report {local_uid} could not be retrieved"
+            )
+        with open(report_file, "r") as f:
+            report_info = yaml.safe_load(f)
+        return report_info
+
+    def display_dict(self):
+        if self.data_path:
+            data_source = f"{self.data_path}"[:27] + "..."
+        elif self.demo_dataset_url:
+            data_source = f"{self.demo_dataset_url}"[:27] + "..."
+        else:
+            data_source = f"{self.prepared_data_hash}"
+
+        return {
+            "UID": self.generated_uid,
+            "Data Source": data_source,
+            "Model": (
+                self.model if isinstance(self.model, int) else self.model[:27] + "..."
+            ),
+            "Evaluator": (
+                self.data_evaluator_mlcube
+                if isinstance(self.data_evaluator_mlcube, int)
+                else self.data_evaluator_mlcube[:27] + "..."
+            ),
+        }
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__generate_uid() + +

+ + +
+ +

A helper that generates a unique hash for a test report.

+ +
+ Source code in cli/medperf/entities/report.py +
44
+45
+46
+47
+48
+49
def __generate_uid(self):
+    """A helper that generates a unique hash for a test report."""
+    params = self.todict()
+    del params["results"]
+    params = str(params)
+    return hashlib.sha256(params.encode()).hexdigest()
+
+
+
+ +
+ + +
+ + + +

+all(local_only=False, mine_only=False) + + + classmethod + + +

+ + +
+ +

Gets and creates instances of test reports. +Arguments are only specified for compatibility with +Entity.List and Entity.View, but they don't contribute to +the logic.

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[TestReport] + +
+

List[TestReport]: List containing all test reports

+
+
+ +
+ Source code in cli/medperf/entities/report.py +
54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
@classmethod
+def all(
+    cls, local_only: bool = False, mine_only: bool = False
+) -> List["TestReport"]:
+    """Gets and creates instances of test reports.
+    Arguments are only specified for compatibility with
+    `Entity.List` and `Entity.View`, but they don't contribute to
+    the logic.
+
+    Returns:
+        List[TestReport]: List containing all test reports
+    """
+    logging.info("Retrieving all reports")
+    reports = []
+    tests_folder = config.tests_folder
+    try:
+        uids = next(os.walk(tests_folder))[1]
+    except StopIteration:
+        msg = "Couldn't iterate over the tests directory"
+        logging.warning(msg)
+        raise RuntimeError(msg)
+
+    for uid in uids:
+        local_meta = cls.__get_local_dict(uid)
+        report = cls(**local_meta)
+        reports.append(report)
+
+    return reports
+
+
+
+ +
+ + +
+ + + +

+get(report_uid) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a TestReport instance obtained the user's machine

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
report_uid + str + +
+

UID of the TestReport instance

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
TestReport + TestReport + +
+

Specified TestReport instance

+
+
+ +
+ Source code in cli/medperf/entities/report.py +
83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
@classmethod
+def get(cls, report_uid: str) -> "TestReport":
+    """Retrieves and creates a TestReport instance obtained the user's machine
+
+    Args:
+        report_uid (str): UID of the TestReport instance
+
+    Returns:
+        TestReport: Specified TestReport instance
+    """
+    logging.debug(f"Retrieving report {report_uid}")
+    report_dict = cls.__get_local_dict(report_uid)
+    report = cls(**report_dict)
+    report.write()
+    return report
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/entities/result/index.html b/reference/entities/result/index.html new file mode 100644 index 000000000..529e15262 --- /dev/null +++ b/reference/entities/result/index.html @@ -0,0 +1,2185 @@ + + + + + + + + + + + + + + + + + + + + Result - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Result

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ Result + + +

+ + +
+

+ Bases: Entity, Uploadable, MedperfSchema, ApprovableSchema

+ + +

Class representing a Result entry

+

Results are obtained after successfully running a benchmark +execution flow. They contain information regarding the +components involved in obtaining metrics results, as well as the +results themselves. This class provides methods for working with +benchmark results and how to upload them to the backend.

+ +
+ Source code in cli/medperf/entities/result.py +
class Result(Entity, Uploadable, MedperfSchema, ApprovableSchema):
+    """
+    Class representing a Result entry
+
+    Results are obtained after successfully running a benchmark
+    execution flow. They contain information regarding the
+    components involved in obtaining metrics results, as well as the
+    results themselves. This class provides methods for working with
+    benchmark results and how to upload them to the backend.
+    """
+
+    benchmark: int
+    model: int
+    dataset: int
+    results: dict
+    metadata: dict = {}
+    user_metadata: dict = {}
+
+    def __init__(self, *args, **kwargs):
+        """Creates a new result instance"""
+        super().__init__(*args, **kwargs)
+
+        self.generated_uid = f"b{self.benchmark}m{self.model}d{self.dataset}"
+        path = config.results_folder
+        if self.id:
+            path = os.path.join(path, str(self.id))
+        else:
+            path = os.path.join(path, self.generated_uid)
+
+        self.path = path
+
+    @classmethod
+    def all(cls, local_only: bool = False, filters: dict = {}) -> List["Result"]:
+        """Gets and creates instances of all the user's results
+
+        Args:
+            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+        Returns:
+            List[Result]: List containing all results
+        """
+        logging.info("Retrieving all results")
+        results = []
+        if not local_only:
+            results = cls.__remote_all(filters=filters)
+
+        remote_uids = set([result.id for result in results])
+
+        local_results = cls.__local_all()
+
+        results += [res for res in local_results if res.id not in remote_uids]
+
+        return results
+
+    @classmethod
+    def __remote_all(cls, filters: dict) -> List["Result"]:
+        results = []
+
+        try:
+            comms_fn = cls.__remote_prefilter(filters)
+            results_meta = comms_fn()
+            results = [cls(**meta) for meta in results_meta]
+        except CommunicationRetrievalError:
+            msg = "Couldn't retrieve all results from the server"
+            logging.warning(msg)
+
+        return results
+
+    @classmethod
+    def __remote_prefilter(cls, filters: dict) -> callable:
+        """Applies filtering logic that must be done before retrieving remote entities
+
+        Args:
+            filters (dict): filters to apply
+
+        Returns:
+            callable: A function for retrieving remote entities with the applied prefilters
+        """
+        comms_fn = config.comms.get_results
+        if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+            comms_fn = config.comms.get_user_results
+        if "benchmark" in filters and filters["benchmark"] is not None:
+            bmk = filters["benchmark"]
+
+            def get_benchmark_results():
+                # Decorate the benchmark results remote function so it has the same signature
+                # as all the comms_fns
+                return config.comms.get_benchmark_results(bmk)
+
+            comms_fn = get_benchmark_results
+
+        return comms_fn
+
+    @classmethod
+    def __local_all(cls) -> List["Result"]:
+        results = []
+        results_folder = config.results_folder
+        try:
+            uids = next(os.walk(results_folder))[1]
+        except StopIteration:
+            msg = "Couldn't iterate over the dataset directory"
+            logging.warning(msg)
+            raise RuntimeError(msg)
+
+        for uid in uids:
+            local_meta = cls.__get_local_dict(uid)
+            result = cls(**local_meta)
+            results.append(result)
+
+        return results
+
+    @classmethod
+    def get(cls, result_uid: Union[str, int], local_only: bool = False) -> "Result":
+        """Retrieves and creates a Result instance obtained from the platform.
+        If the result instance already exists in the user's machine, it loads
+        the local instance
+
+        Args:
+            result_uid (str): UID of the Result instance
+
+        Returns:
+            Result: Specified Result instance
+        """
+        if not str(result_uid).isdigit() or local_only:
+            return cls.__local_get(result_uid)
+
+        try:
+            return cls.__remote_get(result_uid)
+        except CommunicationRetrievalError:
+            logging.warning(f"Getting Result {result_uid} from comms failed")
+            logging.info(f"Looking for result {result_uid} locally")
+            return cls.__local_get(result_uid)
+
+    @classmethod
+    def __remote_get(cls, result_uid: int) -> "Result":
+        """Retrieves and creates a Dataset instance from the comms instance.
+        If the dataset is present in the user's machine then it retrieves it from there.
+
+        Args:
+            result_uid (str): server UID of the dataset
+
+        Returns:
+            Dataset: Specified Dataset Instance
+        """
+        logging.debug(f"Retrieving result {result_uid} remotely")
+        meta = config.comms.get_result(result_uid)
+        result = cls(**meta)
+        result.write()
+        return result
+
+    @classmethod
+    def __local_get(cls, result_uid: Union[str, int]) -> "Result":
+        """Retrieves and creates a Dataset instance from the comms instance.
+        If the dataset is present in the user's machine then it retrieves it from there.
+
+        Args:
+            result_uid (str): server UID of the dataset
+
+        Returns:
+            Dataset: Specified Dataset Instance
+        """
+        logging.debug(f"Retrieving result {result_uid} locally")
+        local_meta = cls.__get_local_dict(result_uid)
+        result = cls(**local_meta)
+        return result
+
+    def todict(self):
+        return self.extended_dict()
+
+    def upload(self):
+        """Uploads the results to the comms
+
+        Args:
+            comms (Comms): Instance of the communications interface.
+        """
+        if self.for_test:
+            raise InvalidArgumentError("Cannot upload test results.")
+        results_info = self.todict()
+        updated_results_info = config.comms.upload_result(results_info)
+        return updated_results_info
+
+    def write(self):
+        result_file = os.path.join(self.path, config.results_info_file)
+        os.makedirs(self.path, exist_ok=True)
+        with open(result_file, "w") as f:
+            yaml.dump(self.todict(), f)
+        return result_file
+
+    @classmethod
+    def __get_local_dict(cls, local_uid):
+        result_path = os.path.join(config.results_folder, str(local_uid))
+        result_file = os.path.join(result_path, config.results_info_file)
+        if not os.path.exists(result_file):
+            raise InvalidArgumentError(
+                f"The requested result {local_uid} could not be retrieved"
+            )
+        with open(result_file, "r") as f:
+            results_info = yaml.safe_load(f)
+        return results_info
+
+    def display_dict(self):
+        return {
+            "UID": self.identifier,
+            "Name": self.name,
+            "Benchmark": self.benchmark,
+            "Model": self.model,
+            "Dataset": self.dataset,
+            "Partial": self.metadata["partial"],
+            "Approval Status": self.approval_status,
+            "Created At": self.created_at,
+            "Registered": self.is_registered,
+        }
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__init__(*args, **kwargs) + +

+ + +
+ +

Creates a new result instance

+ +
+ Source code in cli/medperf/entities/result.py +
31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
def __init__(self, *args, **kwargs):
+    """Creates a new result instance"""
+    super().__init__(*args, **kwargs)
+
+    self.generated_uid = f"b{self.benchmark}m{self.model}d{self.dataset}"
+    path = config.results_folder
+    if self.id:
+        path = os.path.join(path, str(self.id))
+    else:
+        path = os.path.join(path, self.generated_uid)
+
+    self.path = path
+
+
+
+ +
+ + +
+ + + +

+__local_get(result_uid) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Dataset instance from the comms instance. +If the dataset is present in the user's machine then it retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
result_uid + str + +
+

server UID of the dataset

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dataset + Result + +
+

Specified Dataset Instance

+
+
+ +
+ Source code in cli/medperf/entities/result.py +
@classmethod
+def __local_get(cls, result_uid: Union[str, int]) -> "Result":
+    """Retrieves and creates a Dataset instance from the comms instance.
+    If the dataset is present in the user's machine then it retrieves it from there.
+
+    Args:
+        result_uid (str): server UID of the dataset
+
+    Returns:
+        Dataset: Specified Dataset Instance
+    """
+    logging.debug(f"Retrieving result {result_uid} locally")
+    local_meta = cls.__get_local_dict(result_uid)
+    result = cls(**local_meta)
+    return result
+
+
+
+ +
+ + +
+ + + +

+__remote_get(result_uid) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Dataset instance from the comms instance. +If the dataset is present in the user's machine then it retrieves it from there.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
result_uid + str + +
+

server UID of the dataset

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Dataset + Result + +
+

Specified Dataset Instance

+
+
+ +
+ Source code in cli/medperf/entities/result.py +
@classmethod
+def __remote_get(cls, result_uid: int) -> "Result":
+    """Retrieves and creates a Dataset instance from the comms instance.
+    If the dataset is present in the user's machine then it retrieves it from there.
+
+    Args:
+        result_uid (str): server UID of the dataset
+
+    Returns:
+        Dataset: Specified Dataset Instance
+    """
+    logging.debug(f"Retrieving result {result_uid} remotely")
+    meta = config.comms.get_result(result_uid)
+    result = cls(**meta)
+    result.write()
+    return result
+
+
+
+ +
+ + +
+ + + +

+__remote_prefilter(filters) + + + classmethod + + +

+ + +
+ +

Applies filtering logic that must be done before retrieving remote entities

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filters + dict + +
+

filters to apply

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
callable + callable + +
+

A function for retrieving remote entities with the applied prefilters

+
+
+ +
+ Source code in cli/medperf/entities/result.py +
@classmethod
+def __remote_prefilter(cls, filters: dict) -> callable:
+    """Applies filtering logic that must be done before retrieving remote entities
+
+    Args:
+        filters (dict): filters to apply
+
+    Returns:
+        callable: A function for retrieving remote entities with the applied prefilters
+    """
+    comms_fn = config.comms.get_results
+    if "owner" in filters and filters["owner"] == get_medperf_user_data()["id"]:
+        comms_fn = config.comms.get_user_results
+    if "benchmark" in filters and filters["benchmark"] is not None:
+        bmk = filters["benchmark"]
+
+        def get_benchmark_results():
+            # Decorate the benchmark results remote function so it has the same signature
+            # as all the comms_fns
+            return config.comms.get_benchmark_results(bmk)
+
+        comms_fn = get_benchmark_results
+
+    return comms_fn
+
+
+
+ +
+ + +
+ + + +

+all(local_only=False, filters={}) + + + classmethod + + +

+ + +
+ +

Gets and creates instances of all the user's results

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
local_only + bool + +
+

Wether to retrieve only local entities. Defaults to False.

+
+
+ False +
filters + dict + +
+

key-value pairs specifying filters to apply to the list of entities.

+
+
+ {} +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[Result] + +
+

List[Result]: List containing all results

+
+
+ +
+ Source code in cli/medperf/entities/result.py +
44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
@classmethod
+def all(cls, local_only: bool = False, filters: dict = {}) -> List["Result"]:
+    """Gets and creates instances of all the user's results
+
+    Args:
+        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.
+        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.
+
+    Returns:
+        List[Result]: List containing all results
+    """
+    logging.info("Retrieving all results")
+    results = []
+    if not local_only:
+        results = cls.__remote_all(filters=filters)
+
+    remote_uids = set([result.id for result in results])
+
+    local_results = cls.__local_all()
+
+    results += [res for res in local_results if res.id not in remote_uids]
+
+    return results
+
+
+
+ +
+ + +
+ + + +

+get(result_uid, local_only=False) + + + classmethod + + +

+ + +
+ +

Retrieves and creates a Result instance obtained from the platform. +If the result instance already exists in the user's machine, it loads +the local instance

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
result_uid + str + +
+

UID of the Result instance

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
Result + Result + +
+

Specified Result instance

+
+
+ +
+ Source code in cli/medperf/entities/result.py +
@classmethod
+def get(cls, result_uid: Union[str, int], local_only: bool = False) -> "Result":
+    """Retrieves and creates a Result instance obtained from the platform.
+    If the result instance already exists in the user's machine, it loads
+    the local instance
+
+    Args:
+        result_uid (str): UID of the Result instance
+
+    Returns:
+        Result: Specified Result instance
+    """
+    if not str(result_uid).isdigit() or local_only:
+        return cls.__local_get(result_uid)
+
+    try:
+        return cls.__remote_get(result_uid)
+    except CommunicationRetrievalError:
+        logging.warning(f"Getting Result {result_uid} from comms failed")
+        logging.info(f"Looking for result {result_uid} locally")
+        return cls.__local_get(result_uid)
+
+
+
+ +
+ + +
+ + + +

+upload() + +

+ + +
+ +

Uploads the results to the comms

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
comms + Comms + +
+

Instance of the communications interface.

+
+
+ required +
+ +
+ Source code in cli/medperf/entities/result.py +
def upload(self):
+    """Uploads the results to the comms
+
+    Args:
+        comms (Comms): Instance of the communications interface.
+    """
+    if self.for_test:
+        raise InvalidArgumentError("Cannot upload test results.")
+    results_info = self.todict()
+    updated_results_info = config.comms.upload_result(results_info)
+    return updated_results_info
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/entities/schemas/index.html b/reference/entities/schemas/index.html new file mode 100644 index 000000000..839af645c --- /dev/null +++ b/reference/entities/schemas/index.html @@ -0,0 +1,1332 @@ + + + + + + + + + + + + + + + + + + + + Schemas - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Schemas

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ MedperfBaseSchema + + +

+ + +
+

+ Bases: BaseModel

+ + +
+ Source code in cli/medperf/entities/schemas.py +
11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
class MedperfBaseSchema(BaseModel):
+    def __init__(self, *args, **kwargs):
+        """Override the ValidationError procedure so we can
+        format the error message in our desired way
+        """
+        try:
+            super().__init__(*args, **kwargs)
+        except ValidationError as e:
+            errors_dict = defaultdict(list)
+            for error in e.errors():
+                field = error["loc"]
+                msg = error["msg"]
+                errors_dict[field].append(msg)
+
+            error_msg = "Field Validation Error:"
+            error_msg += format_errors_dict(errors_dict)
+
+            raise MedperfException(error_msg)
+
+    def dict(self, *args, **kwargs) -> dict:
+        """Overrides dictionary implementation so it filters out
+        fields not defined in the pydantic model
+
+        Returns:
+            dict: filtered dictionary
+        """
+        fields = self.__fields__
+        valid_fields = []
+        # Gather all the field names, both original an alias names
+        for field_name, field_item in fields.items():
+            valid_fields.append(field_name)
+            valid_fields.append(field_item.alias)
+        # Remove duplicates
+        valid_fields = set(valid_fields)
+        model_dict = super().dict(*args, **kwargs)
+        out_dict = {k: v for k, v in model_dict.items() if k in valid_fields}
+        return out_dict
+
+    def extended_dict(self) -> dict:
+        """Dictionary containing both original and alias fields
+
+        Returns:
+            dict: Extended dictionary representation
+        """
+        og_dict = self.dict()
+        alias_dict = self.dict(by_alias=True)
+        og_dict.update(alias_dict)
+        for k, v in og_dict.items():
+            if v is None:
+                og_dict[k] = ""
+            if isinstance(v, HttpUrl):
+                og_dict[k] = str(v)
+        return og_dict
+
+    @validator("*", pre=True)
+    def empty_str_to_none(cls, v):
+        if v == "":
+            return None
+        return v
+
+    class Config:
+        allow_population_by_field_name = True
+        extra = "allow"
+        use_enum_values = True
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+__init__(*args, **kwargs) + +

+ + +
+ +

Override the ValidationError procedure so we can +format the error message in our desired way

+ +
+ Source code in cli/medperf/entities/schemas.py +
12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
def __init__(self, *args, **kwargs):
+    """Override the ValidationError procedure so we can
+    format the error message in our desired way
+    """
+    try:
+        super().__init__(*args, **kwargs)
+    except ValidationError as e:
+        errors_dict = defaultdict(list)
+        for error in e.errors():
+            field = error["loc"]
+            msg = error["msg"]
+            errors_dict[field].append(msg)
+
+        error_msg = "Field Validation Error:"
+        error_msg += format_errors_dict(errors_dict)
+
+        raise MedperfException(error_msg)
+
+
+
+ +
+ + +
+ + + +

+dict(*args, **kwargs) + +

+ + +
+ +

Overrides dictionary implementation so it filters out +fields not defined in the pydantic model

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

filtered dictionary

+
+
+ +
+ Source code in cli/medperf/entities/schemas.py +
30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
def dict(self, *args, **kwargs) -> dict:
+    """Overrides dictionary implementation so it filters out
+    fields not defined in the pydantic model
+
+    Returns:
+        dict: filtered dictionary
+    """
+    fields = self.__fields__
+    valid_fields = []
+    # Gather all the field names, both original an alias names
+    for field_name, field_item in fields.items():
+        valid_fields.append(field_name)
+        valid_fields.append(field_item.alias)
+    # Remove duplicates
+    valid_fields = set(valid_fields)
+    model_dict = super().dict(*args, **kwargs)
+    out_dict = {k: v for k, v in model_dict.items() if k in valid_fields}
+    return out_dict
+
+
+
+ +
+ + +
+ + + +

+extended_dict() + +

+ + +
+ +

Dictionary containing both original and alias fields

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

Extended dictionary representation

+
+
+ +
+ Source code in cli/medperf/entities/schemas.py +
49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
def extended_dict(self) -> dict:
+    """Dictionary containing both original and alias fields
+
+    Returns:
+        dict: Extended dictionary representation
+    """
+    og_dict = self.dict()
+    alias_dict = self.dict(by_alias=True)
+    og_dict.update(alias_dict)
+    for k, v in og_dict.items():
+        if v is None:
+            og_dict[k] = ""
+        if isinstance(v, HttpUrl):
+            og_dict[k] = str(v)
+    return og_dict
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/enums/index.html b/reference/enums/index.html new file mode 100644 index 000000000..d8ba40960 --- /dev/null +++ b/reference/enums/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Enums - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Enums

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/exceptions/index.html b/reference/exceptions/index.html new file mode 100644 index 000000000..e6deeee69 --- /dev/null +++ b/reference/exceptions/index.html @@ -0,0 +1,1271 @@ + + + + + + + + + + + + + + + + + + + + Exceptions - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Exceptions

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ CleanExit + + +

+ + +
+

+ Bases: MedperfException

+ + +

Raised when Medperf needs to stop for non erroneous reasons

+ +
+ Source code in cli/medperf/exceptions.py +
33
+34
+35
+36
+37
+38
class CleanExit(MedperfException):
+    """Raised when Medperf needs to stop for non erroneous reasons"""
+
+    def __init__(self, *args, medperf_status_code=0) -> None:
+        super().__init__(*args)
+        self.medperf_status_code = medperf_status_code
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ +
+ + + +

+ CommunicationAuthenticationError + + +

+ + +
+

+ Bases: CommunicationError

+ + +

Raised when the communication interface can't handle an authentication request

+ +
+ Source code in cli/medperf/exceptions.py +
class CommunicationAuthenticationError(CommunicationError):
+    """Raised when the communication interface can't handle an authentication request"""
+
+
+ +
+ +
+ +
+ + + +

+ CommunicationError + + +

+ + +
+

+ Bases: MedperfException

+ + +

Raised when an error happens due to the communication interface

+ +
+ Source code in cli/medperf/exceptions.py +
5
+6
class CommunicationError(MedperfException):
+    """Raised when an error happens due to the communication interface"""
+
+
+ +
+ +
+ +
+ + + +

+ CommunicationRequestError + + +

+ + +
+

+ Bases: CommunicationError

+ + +

Raised when the communication interface can't handle a request appropiately

+ +
+ Source code in cli/medperf/exceptions.py +
class CommunicationRequestError(CommunicationError):
+    """Raised when the communication interface can't handle a request appropiately"""
+
+
+ +
+ +
+ +
+ + + +

+ CommunicationRetrievalError + + +

+ + +
+

+ Bases: CommunicationError

+ + +

Raised when the communication interface can't retrieve an element

+ +
+ Source code in cli/medperf/exceptions.py +
class CommunicationRetrievalError(CommunicationError):
+    """Raised when the communication interface can't retrieve an element"""
+
+
+ +
+ +
+ +
+ + + +

+ ExecutionError + + +

+ + +
+

+ Bases: MedperfException

+ + +

Raised when an execution component fails

+ +
+ Source code in cli/medperf/exceptions.py +
class ExecutionError(MedperfException):
+    """Raised when an execution component fails"""
+
+
+ +
+ +
+ +
+ + + +

+ InvalidArgumentError + + +

+ + +
+

+ Bases: MedperfException

+ + +

Raised when an argument or set of arguments are consided invalid

+ +
+ Source code in cli/medperf/exceptions.py +
class InvalidArgumentError(MedperfException):
+    """Raised when an argument or set of arguments are consided invalid"""
+
+
+ +
+ +
+ +
+ + + +

+ InvalidEntityError + + +

+ + +
+

+ Bases: MedperfException

+ + +

Raised when an entity is considered invalid

+ +
+ Source code in cli/medperf/exceptions.py +
class InvalidEntityError(MedperfException):
+    """Raised when an entity is considered invalid"""
+
+
+ +
+ +
+ +
+ + + +

+ MedperfException + + +

+ + +
+

+ Bases: Exception

+ + +

Medperf base exception

+ +
+ Source code in cli/medperf/exceptions.py +
1
+2
class MedperfException(Exception):
+    """Medperf base exception"""
+
+
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/init/index.html b/reference/init/index.html new file mode 100644 index 000000000..094fd07dd --- /dev/null +++ b/reference/init/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Init - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Init

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/storage/utils/index.html b/reference/storage/utils/index.html new file mode 100644 index 000000000..e1b38ec7e --- /dev/null +++ b/reference/storage/utils/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Utils - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Utils

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/ui/cli/index.html b/reference/ui/cli/index.html new file mode 100644 index 000000000..c565c1623 --- /dev/null +++ b/reference/ui/cli/index.html @@ -0,0 +1,1848 @@ + + + + + + + + + + + + + + + + + + + + Cli - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Cli

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ CLI + + +

+ + +
+

+ Bases: UI

+ + +
+ Source code in cli/medperf/ui/cli.py +
class CLI(UI):
+    def __init__(self):
+        self.spinner = yaspin(color="green")
+        self.is_interactive = False
+
+    def print(self, msg: str = ""):
+        """Display a message on the command line
+
+        Args:
+            msg (str): message to print
+        """
+        self.__print(msg)
+
+    def print_error(self, msg: str):
+        """Display an error message on the command line
+
+        Args:
+            msg (str): error message to display
+        """
+        msg = f"❌ {msg}"
+        msg = typer.style(msg, fg=typer.colors.RED, bold=True)
+        self.__print(msg)
+
+    def print_warning(self, msg: str):
+        """Display a warning message on the command line
+
+        Args:
+            msg (str): warning message to display
+        """
+        msg = typer.style(msg, fg=typer.colors.YELLOW, bold=True)
+        self.__print(msg)
+
+    def __print(self, msg: str = ""):
+        if self.is_interactive:
+            self.spinner.write(msg)
+        else:
+            typer.echo(msg)
+
+    def start_interactive(self):
+        """Start an interactive session where messages can be overwritten
+        and animations can be displayed"""
+        self.is_interactive = True
+        self.spinner.start()
+
+    def stop_interactive(self):
+        """Stop an interactive session"""
+        self.is_interactive = False
+        self.spinner.stop()
+
+    @contextmanager
+    def interactive(self):
+        """Context managed interactive session.
+
+        Yields:
+            CLI: Yields the current CLI instance with an interactive session initialized
+        """
+        self.start_interactive()
+        try:
+            yield self
+        finally:
+            self.stop_interactive()
+
+    @property
+    def text(self):
+        return self.spinner.text
+
+    @text.setter
+    def text(self, msg: str = ""):
+        """Displays a message that overwrites previous messages if they
+        were created during an interactive ui session.
+
+        If not on interactive session already, then it calls the ui print function
+
+        Args:
+            msg (str): message to display
+        """
+        if not self.is_interactive:
+            self.print(msg)
+
+        self.spinner.text = msg
+
+    def prompt(self, msg: str) -> str:
+        """Displays a prompt to the user and waits for an answer
+
+        Args:
+            msg (str): message to use for the prompt
+
+        Returns:
+            str: user input
+        """
+        return input(msg)
+
+    def hidden_prompt(self, msg: str) -> str:
+        """Displays a prompt to the user and waits for an aswer. User input is not displayed
+
+        Args:
+            msg (str): message to use for the prompt
+
+        Returns:
+            str: user input
+        """
+        return getpass(msg)
+
+    def print_highlight(self, msg: str = ""):
+        """Display a highlighted message
+
+        Args:
+            msg (str): message to print
+        """
+        msg = typer.style(msg, fg=typer.colors.GREEN)
+        self.__print(msg)
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+hidden_prompt(msg) + +

+ + +
+ +

Displays a prompt to the user and waits for an aswer. User input is not displayed

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

message to use for the prompt

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

user input

+
+
+ +
+ Source code in cli/medperf/ui/cli.py +
def hidden_prompt(self, msg: str) -> str:
+    """Displays a prompt to the user and waits for an aswer. User input is not displayed
+
+    Args:
+        msg (str): message to use for the prompt
+
+    Returns:
+        str: user input
+    """
+    return getpass(msg)
+
+
+
+ +
+ + +
+ + + +

+interactive() + +

+ + +
+ +

Context managed interactive session.

+ + + +

Yields:

+ + + + + + + + + + + + + +
Name TypeDescription
CLI + +
+

Yields the current CLI instance with an interactive session initialized

+
+
+ +
+ Source code in cli/medperf/ui/cli.py +
58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
@contextmanager
+def interactive(self):
+    """Context managed interactive session.
+
+    Yields:
+        CLI: Yields the current CLI instance with an interactive session initialized
+    """
+    self.start_interactive()
+    try:
+        yield self
+    finally:
+        self.stop_interactive()
+
+
+
+ +
+ + +
+ + + +

+print(msg='') + +

+ + +
+ +

Display a message on the command line

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

message to print

+
+
+ '' +
+ +
+ Source code in cli/medperf/ui/cli.py +
14
+15
+16
+17
+18
+19
+20
def print(self, msg: str = ""):
+    """Display a message on the command line
+
+    Args:
+        msg (str): message to print
+    """
+    self.__print(msg)
+
+
+
+ +
+ + +
+ + + +

+print_error(msg) + +

+ + +
+ +

Display an error message on the command line

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

error message to display

+
+
+ required +
+ +
+ Source code in cli/medperf/ui/cli.py +
22
+23
+24
+25
+26
+27
+28
+29
+30
def print_error(self, msg: str):
+    """Display an error message on the command line
+
+    Args:
+        msg (str): error message to display
+    """
+    msg = f"❌ {msg}"
+    msg = typer.style(msg, fg=typer.colors.RED, bold=True)
+    self.__print(msg)
+
+
+
+ +
+ + +
+ + + +

+print_highlight(msg='') + +

+ + +
+ +

Display a highlighted message

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

message to print

+
+
+ '' +
+ +
+ Source code in cli/medperf/ui/cli.py +
def print_highlight(self, msg: str = ""):
+    """Display a highlighted message
+
+    Args:
+        msg (str): message to print
+    """
+    msg = typer.style(msg, fg=typer.colors.GREEN)
+    self.__print(msg)
+
+
+
+ +
+ + +
+ + + +

+print_warning(msg) + +

+ + +
+ +

Display a warning message on the command line

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

warning message to display

+
+
+ required +
+ +
+ Source code in cli/medperf/ui/cli.py +
32
+33
+34
+35
+36
+37
+38
+39
def print_warning(self, msg: str):
+    """Display a warning message on the command line
+
+    Args:
+        msg (str): warning message to display
+    """
+    msg = typer.style(msg, fg=typer.colors.YELLOW, bold=True)
+    self.__print(msg)
+
+
+
+ +
+ + +
+ + + +

+prompt(msg) + +

+ + +
+ +

Displays a prompt to the user and waits for an answer

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

message to use for the prompt

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

user input

+
+
+ +
+ Source code in cli/medperf/ui/cli.py +
90
+91
+92
+93
+94
+95
+96
+97
+98
+99
def prompt(self, msg: str) -> str:
+    """Displays a prompt to the user and waits for an answer
+
+    Args:
+        msg (str): message to use for the prompt
+
+    Returns:
+        str: user input
+    """
+    return input(msg)
+
+
+
+ +
+ + +
+ + + +

+start_interactive() + +

+ + +
+ +

Start an interactive session where messages can be overwritten +and animations can be displayed

+ +
+ Source code in cli/medperf/ui/cli.py +
47
+48
+49
+50
+51
def start_interactive(self):
+    """Start an interactive session where messages can be overwritten
+    and animations can be displayed"""
+    self.is_interactive = True
+    self.spinner.start()
+
+
+
+ +
+ + +
+ + + +

+stop_interactive() + +

+ + +
+ +

Stop an interactive session

+ +
+ Source code in cli/medperf/ui/cli.py +
53
+54
+55
+56
def stop_interactive(self):
+    """Stop an interactive session"""
+    self.is_interactive = False
+    self.spinner.stop()
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/ui/factory/index.html b/reference/ui/factory/index.html new file mode 100644 index 000000000..f0011787f --- /dev/null +++ b/reference/ui/factory/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + Factory - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Factory

+ +
+ + + +
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/ui/interface/index.html b/reference/ui/interface/index.html new file mode 100644 index 000000000..d39d3671b --- /dev/null +++ b/reference/ui/interface/index.html @@ -0,0 +1,1649 @@ + + + + + + + + + + + + + + + + + + + + Interface - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Interface

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ UI + + +

+ + +
+

+ Bases: ABC

+ + +
+ Source code in cli/medperf/ui/interface.py +
 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
class UI(ABC):
+    @abstractmethod
+    def print(self, msg: str = ""):
+        """Display a message to the interface. If on interactive session overrides
+        previous message
+        """
+
+    @abstractmethod
+    def print_error(self, msg: str):
+        """Display an error message to the interface"""
+
+    def print_warning(self, msg: str):
+        """Display a warning message on the command line"""
+
+    @abstractmethod
+    def start_interactive(self):
+        """Initialize an interactive session for animations or overriding messages.
+        If the UI doesn't support this, the function can be left empty.
+        """
+
+    @abstractmethod
+    def stop_interactive(self):
+        """Terminate an interactive session.
+        If the UI doesn't support this, the function can be left empty.
+        """
+
+    @abstractmethod
+    @contextmanager
+    def interactive(self):
+        """Context managed interactive session. Expected to yield the same instance"""
+
+    @abstractmethod
+    def text(self, msg: str):
+        """Displays a messages that overwrites previous messages if they were created
+        during an interactive session.
+        If not supported or not on an interactive session, it is expected to fallback
+        to the UI print function.
+
+        Args:
+            msg (str): message to display
+        """
+
+    @abstractmethod
+    def prompt(msg: str) -> str:
+        """Displays a prompt to the user and waits for an answer"""
+
+    @abstractmethod
+    def hidden_prompt(self, msg: str) -> str:
+        """Displays a prompt to the user and waits for an aswer. User input is not displayed
+
+        Args:
+            msg (str): message to use for the prompt
+
+        Returns:
+            str: user input
+        """
+
+    @abstractmethod
+    def print_highlight(self, msg: str = ""):
+        """Display a message on the command line with green color
+
+        Args:
+            msg (str): message to print
+        """
+
+
+ + + +
+ + + + + + + + + + +
+ + + +

+hidden_prompt(msg) + + + abstractmethod + + +

+ + +
+ +

Displays a prompt to the user and waits for an aswer. User input is not displayed

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

message to use for the prompt

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

user input

+
+
+ +
+ Source code in cli/medperf/ui/interface.py +
51
+52
+53
+54
+55
+56
+57
+58
+59
+60
@abstractmethod
+def hidden_prompt(self, msg: str) -> str:
+    """Displays a prompt to the user and waits for an aswer. User input is not displayed
+
+    Args:
+        msg (str): message to use for the prompt
+
+    Returns:
+        str: user input
+    """
+
+
+
+ +
+ + +
+ + + +

+interactive() + + + abstractmethod + + +

+ + +
+ +

Context managed interactive session. Expected to yield the same instance

+ +
+ Source code in cli/medperf/ui/interface.py +
31
+32
+33
+34
@abstractmethod
+@contextmanager
+def interactive(self):
+    """Context managed interactive session. Expected to yield the same instance"""
+
+
+
+ +
+ + +
+ + + +

+print(msg='') + + + abstractmethod + + +

+ + +
+ +

Display a message to the interface. If on interactive session overrides +previous message

+ +
+ Source code in cli/medperf/ui/interface.py +
 6
+ 7
+ 8
+ 9
+10
@abstractmethod
+def print(self, msg: str = ""):
+    """Display a message to the interface. If on interactive session overrides
+    previous message
+    """
+
+
+
+ +
+ + +
+ + + +

+print_error(msg) + + + abstractmethod + + +

+ + +
+ +

Display an error message to the interface

+ +
+ Source code in cli/medperf/ui/interface.py +
12
+13
+14
@abstractmethod
+def print_error(self, msg: str):
+    """Display an error message to the interface"""
+
+
+
+ +
+ + +
+ + + +

+print_highlight(msg='') + + + abstractmethod + + +

+ + +
+ +

Display a message on the command line with green color

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

message to print

+
+
+ '' +
+ +
+ Source code in cli/medperf/ui/interface.py +
62
+63
+64
+65
+66
+67
+68
@abstractmethod
+def print_highlight(self, msg: str = ""):
+    """Display a message on the command line with green color
+
+    Args:
+        msg (str): message to print
+    """
+
+
+
+ +
+ + +
+ + + +

+print_warning(msg) + +

+ + +
+ +

Display a warning message on the command line

+ +
+ Source code in cli/medperf/ui/interface.py +
def print_warning(self, msg: str):
+    """Display a warning message on the command line"""
+
+
+
+ +
+ + +
+ + + +

+prompt(msg) + + + abstractmethod + + +

+ + +
+ +

Displays a prompt to the user and waits for an answer

+ +
+ Source code in cli/medperf/ui/interface.py +
47
+48
+49
@abstractmethod
+def prompt(msg: str) -> str:
+    """Displays a prompt to the user and waits for an answer"""
+
+
+
+ +
+ + +
+ + + +

+start_interactive() + + + abstractmethod + + +

+ + +
+ +

Initialize an interactive session for animations or overriding messages. +If the UI doesn't support this, the function can be left empty.

+ +
+ Source code in cli/medperf/ui/interface.py +
19
+20
+21
+22
+23
@abstractmethod
+def start_interactive(self):
+    """Initialize an interactive session for animations or overriding messages.
+    If the UI doesn't support this, the function can be left empty.
+    """
+
+
+
+ +
+ + +
+ + + +

+stop_interactive() + + + abstractmethod + + +

+ + +
+ +

Terminate an interactive session. +If the UI doesn't support this, the function can be left empty.

+ +
+ Source code in cli/medperf/ui/interface.py +
25
+26
+27
+28
+29
@abstractmethod
+def stop_interactive(self):
+    """Terminate an interactive session.
+    If the UI doesn't support this, the function can be left empty.
+    """
+
+
+
+ +
+ + +
+ + + +

+text(msg) + + + abstractmethod + + +

+ + +
+ +

Displays a messages that overwrites previous messages if they were created +during an interactive session. +If not supported or not on an interactive session, it is expected to fallback +to the UI print function.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

message to display

+
+
+ required +
+ +
+ Source code in cli/medperf/ui/interface.py +
36
+37
+38
+39
+40
+41
+42
+43
+44
+45
@abstractmethod
+def text(self, msg: str):
+    """Displays a messages that overwrites previous messages if they were created
+    during an interactive session.
+    If not supported or not on an interactive session, it is expected to fallback
+    to the UI print function.
+
+    Args:
+        msg (str): message to display
+    """
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/ui/stdin/index.html b/reference/ui/stdin/index.html new file mode 100644 index 000000000..510fe4700 --- /dev/null +++ b/reference/ui/stdin/index.html @@ -0,0 +1,1040 @@ + + + + + + + + + + + + + + + + + + + + Stdin - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Stdin

+ +
+ + + +
+ + + +
+ + + + + + + + +
+ + + +

+ StdIn + + +

+ + +
+

+ Bases: UI

+ + +

Class for using sys.stdin/sys.stdout exclusively. Used mainly for automating +execution with class-like objects. Using only basic IO methods ensures that +piping from the command-line. Should not be used in normal execution, as +hidden prompts and interactive prints will not work as expected.

+ +
+ Source code in cli/medperf/ui/stdin.py +
 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
class StdIn(UI):
+    """
+    Class for using sys.stdin/sys.stdout exclusively. Used mainly for automating
+    execution with class-like objects. Using only basic IO methods ensures that
+    piping from the command-line. Should not be used in normal execution, as
+    hidden prompts and interactive prints will not work as expected.
+    """
+
+    def print(self, msg: str = ""):
+        return print(msg)
+
+    def print_error(self, msg: str):
+        return self.print(msg)
+
+    def start_interactive(self):
+        pass
+
+    def stop_interactive(self):
+        pass
+
+    @contextmanager
+    def interactive(self):
+        yield self
+
+    @property
+    def text(self):
+        return ""
+
+    @text.setter
+    def text(self, msg: str = ""):
+        return
+
+    def prompt(self, msg: str) -> str:
+        return input(msg)
+
+    def hidden_prompt(self, msg: str) -> str:
+        return self.prompt(msg)
+
+
+ + + +
+ + + + + + + + + + + +
+ +
+ +
+ + + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/reference/utils/index.html b/reference/utils/index.html new file mode 100644 index 000000000..ead447102 --- /dev/null +++ b/reference/utils/index.html @@ -0,0 +1,2643 @@ + + + + + + + + + + + + + + + + + + + + Utils - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + +

Utils

+ +
+ + + +
+ + + +
+ + + + + + + + + + +
+ + + +

+approval_prompt(msg) + +

+ + +
+ +

Helper function for prompting the user for things they have to explicitly approve.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

What message to ask the user for approval.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
bool + bool + +
+

Wether the user explicitly approved or not.

+
+
+ +
+ Source code in cli/medperf/utils.py +
def approval_prompt(msg: str) -> bool:
+    """Helper function for prompting the user for things they have to explicitly approve.
+
+    Args:
+        msg (str): What message to ask the user for approval.
+
+    Returns:
+        bool: Wether the user explicitly approved or not.
+    """
+    logging.info("Prompting for user's approval")
+    ui = config.ui
+    approval = None
+    while approval is None or approval not in "yn":
+        approval = ui.prompt(msg.strip() + " ").lower()
+    logging.info(f"User answered approval with {approval}")
+    return approval == "y"
+
+
+
+ +
+ + +
+ + + +

+check_for_updates() + +

+ + +
+ +

Check if the current branch is up-to-date with its remote counterpart using GitPython.

+ +
+ Source code in cli/medperf/utils.py +
def check_for_updates() -> None:
+    """Check if the current branch is up-to-date with its remote counterpart using GitPython."""
+    repo = Repo(config.BASE_DIR)
+    if repo.bare:
+        logging.debug("Repo is bare")
+        return
+
+    logging.debug(f"Current git commit: {repo.head.commit.hexsha}")
+
+    try:
+        for remote in repo.remotes:
+            remote.fetch()
+
+        if repo.head.is_detached:
+            logging.debug("Repo is in detached state")
+            return
+
+        current_branch = repo.active_branch
+        tracking_branch = current_branch.tracking_branch()
+
+        if tracking_branch is None:
+            logging.debug("Current branch does not track a remote branch.")
+            return
+        if current_branch.commit.hexsha == tracking_branch.commit.hexsha:
+            logging.debug("No git branch updates.")
+            return
+
+        logging.debug(
+            f"Git branch updates found: {current_branch.commit.hexsha} -> {tracking_branch.commit.hexsha}"
+        )
+        config.ui.print_warning(
+            "MedPerf client updates found. Please, update your MedPerf installation."
+        )
+    except GitCommandError as e:
+        logging.debug(
+            "Exception raised during updates check. Maybe user checked out repo with git@ and private key"
+            " or repo is in detached / non-tracked state?"
+        )
+        logging.debug(e)
+
+
+
+ +
+ + +
+ + + +

+cleanup() + +

+ + +
+ +

Removes clutter and unused files from the medperf folder structure.

+ +
+ Source code in cli/medperf/utils.py +
84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
def cleanup():
+    """Removes clutter and unused files from the medperf folder structure."""
+    if not config.cleanup:
+        logging.info("Cleanup disabled")
+        return
+
+    for path in config.tmp_paths:
+        remove_path(path)
+
+    trash_folder = config.trash_folder
+    if os.path.exists(trash_folder) and os.listdir(trash_folder):
+        msg = "WARNING: Failed to premanently cleanup some files. Consider deleting"
+        msg += f" '{trash_folder}' manually to avoid unnecessary storage."
+        config.ui.print_warning(msg)
+
+
+
+ +
+ + +
+ + + +

+combine_proc_sp_text(proc) + +

+ + +
+ +

Combines the output of a process and the spinner. +Joins any string captured from the process with the +spinner current text. Any strings ending with any other +character from the subprocess will be returned later.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
proc + spawn + +
+

a pexpect spawned child

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

all non-carriage-return-ending string captured from proc

+
+
+ +
+ Source code in cli/medperf/utils.py +
def combine_proc_sp_text(proc: spawn) -> str:
+    """Combines the output of a process and the spinner.
+    Joins any string captured from the process with the
+    spinner current text. Any strings ending with any other
+    character from the subprocess will be returned later.
+
+    Args:
+        proc (spawn): a pexpect spawned child
+
+    Returns:
+        str: all non-carriage-return-ending string captured from proc
+    """
+
+    ui = config.ui
+    proc_out = ""
+    break_ = False
+    log_filter = _MLCubeOutputFilter(proc.pid)
+
+    while not break_:
+        if not proc.isalive():
+            break_ = True
+        try:
+            line = proc.readline()
+        except TIMEOUT:
+            logging.error("Process timed out")
+            logging.debug(proc_out)
+            raise ExecutionError("Process timed out")
+        line = line.decode("utf-8", "ignore")
+
+        if not line:
+            continue
+
+        # Always log each line just in case the final proc_out
+        # wasn't logged for some reason
+        logging.debug(line)
+        proc_out += line
+        if not log_filter.check_line(line):
+            ui.print(f"{Fore.WHITE}{Style.DIM}{line.strip()}{Style.RESET_ALL}")
+
+    logging.debug("MLCube process finished")
+    logging.debug(proc_out)
+    return proc_out
+
+
+
+ +
+ + +
+ + + +

+dict_pretty_print(in_dict, skip_none_values=True) + +

+ + +
+ +

Helper function for distinctively printing dictionaries with yaml format.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
in_dict + dict + +
+

dictionary to print

+
+
+ required +
skip_none_values + bool + +
+

if fields with None values should be omitted

+
+
+ True +
+ +
+ Source code in cli/medperf/utils.py +
def dict_pretty_print(in_dict: dict, skip_none_values: bool = True):
+    """Helper function for distinctively printing dictionaries with yaml format.
+
+    Args:
+        in_dict (dict): dictionary to print
+        skip_none_values (bool): if fields with `None` values should be omitted
+    """
+    logging.debug(f"Printing dictionary to the user: {in_dict}")
+    ui = config.ui
+    ui.print()
+    ui.print("=" * 20)
+    if skip_none_values:
+        in_dict = {k: v for (k, v) in in_dict.items() if v is not None}
+    ui.print(yaml.dump(in_dict))
+    logging.debug(f"Dictionary printed to the user: {in_dict}")
+    ui.print("=" * 20)
+
+
+
+ +
+ + +
+ + + +

+filter_latest_associations(associations, entity_key) + +

+ + +
+ +

Given a list of entity-benchmark associations, this function +retrieves a list containing the latest association of each +entity instance.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
associations + list[dict] + +
+

the list of associations

+
+
+ required +
entity_key + str + +
+

either "dataset" or "model_mlcube"

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ +
+

list[dict]: the list containing the latest association of each + entity instance.

+
+
+ +
+ Source code in cli/medperf/utils.py +
def filter_latest_associations(associations, entity_key):
+    """Given a list of entity-benchmark associations, this function
+    retrieves a list containing the latest association of each
+    entity instance.
+
+    Args:
+        associations (list[dict]): the list of associations
+        entity_key (str): either "dataset" or "model_mlcube"
+
+    Returns:
+        list[dict]: the list containing the latest association of each
+                    entity instance.
+    """
+
+    associations.sort(key=lambda assoc: parse_datetime(assoc["created_at"]))
+    latest_associations = {}
+    for assoc in associations:
+        entity_id = assoc[entity_key]
+        latest_associations[entity_id] = assoc
+
+    latest_associations = list(latest_associations.values())
+    return latest_associations
+
+
+
+ +
+ + +
+ + + +

+format_errors_dict(errors_dict) + +

+ + +
+ +

Reformats the error details from a field-error(s) dictionary into a human-readable string for printing

+ +
+ Source code in cli/medperf/utils.py +
def format_errors_dict(errors_dict: dict):
+    """Reformats the error details from a field-error(s) dictionary into a human-readable string for printing"""
+    error_msg = ""
+    for field, errors in errors_dict.items():
+        error_msg += "\n"
+        if isinstance(field, tuple):
+            field = field[0]
+        error_msg += f"- {field}: "
+        if isinstance(errors, str):
+            error_msg += errors
+        elif len(errors) == 1:
+            # If a single error for a field is given, don't create a sublist
+            error_msg += errors[0]
+        else:
+            # Create a sublist otherwise
+            for e_msg in errors:
+                error_msg += "\n"
+                error_msg += f"\t- {e_msg}"
+    return error_msg
+
+
+
+ +
+ + +
+ + + +

+generate_tmp_path() + +

+ + +
+ +

Generates a temporary path by means of getting the current timestamp +with a random salt

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

generated temporary path

+
+
+ +
+ Source code in cli/medperf/utils.py +
def generate_tmp_path() -> str:
+    """Generates a temporary path by means of getting the current timestamp
+    with a random salt
+
+    Returns:
+        str: generated temporary path
+    """
+    tmp_path = os.path.join(config.tmp_folder, generate_tmp_uid())
+    config.tmp_paths.append(tmp_path)
+    return tmp_path
+
+
+
+ +
+ + +
+ + + +

+generate_tmp_uid() + +

+ + +
+ +

Generates a temporary uid by means of getting the current timestamp +with a random salt

+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

generated temporary uid

+
+
+ +
+ Source code in cli/medperf/utils.py +
def generate_tmp_uid() -> str:
+    """Generates a temporary uid by means of getting the current timestamp
+    with a random salt
+
+    Returns:
+        str: generated temporary uid
+    """
+    dt = datetime.utcnow()
+    ts_int = int(datetime.timestamp(dt))
+    salt = random.randint(-ts_int, ts_int)
+    ts = str(ts_int + salt)
+    return ts
+
+
+
+ +
+ + +
+ + + +

+get_cube_image_name(cube_path) + +

+ + +
+ +

Retrieves the singularity image name of the mlcube by reading its mlcube.yaml file

+ +
+ Source code in cli/medperf/utils.py +
def get_cube_image_name(cube_path: str) -> str:
+    """Retrieves the singularity image name of the mlcube by reading its mlcube.yaml file"""
+    cube_config_path = os.path.join(cube_path, config.cube_filename)
+    with open(cube_config_path, "r") as f:
+        cube_config = yaml.safe_load(f)
+
+    try:
+        # TODO: Why do we check singularity only there? Why not docker?
+        return cube_config["singularity"]["image"]
+    except KeyError:
+        msg = "The provided mlcube doesn't seem to be configured for singularity"
+        raise MedperfException(msg)
+
+
+
+ +
+ + +
+ + + +

+get_file_hash(path) + +

+ + +
+ +

Calculates the sha256 hash for a given file.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
path + str + +
+

Location of the file of interest.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

Calculated hash

+
+
+ +
+ Source code in cli/medperf/utils.py +
27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
def get_file_hash(path: str) -> str:
+    """Calculates the sha256 hash for a given file.
+
+    Args:
+        path (str): Location of the file of interest.
+
+    Returns:
+        str: Calculated hash
+    """
+    logging.debug("Calculating hash for file {}".format(path))
+    BUF_SIZE = 65536
+    sha = hashlib.sha256()
+    with open(path, "rb") as f:
+        while True:
+            data = f.read(BUF_SIZE)
+            if not data:
+                break
+            sha.update(data)
+
+    sha_val = sha.hexdigest()
+    logging.debug(f"Hash for file {path}: {sha_val}")
+    return sha_val
+
+
+
+ +
+ + +
+ + + +

+get_folders_hash(paths) + +

+ + +
+ +

Generates a hash for all the contents of the fiven folders. This procedure +hashes all the files in all passed folders, sorts them and then hashes that list.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
paths + List(str + +
+

Folders to hash.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

sha256 hash that represents all the folders altogether

+
+
+ +
+ Source code in cli/medperf/utils.py +
def get_folders_hash(paths: List[str]) -> str:
+    """Generates a hash for all the contents of the fiven folders. This procedure
+    hashes all the files in all passed folders, sorts them and then hashes that list.
+
+    Args:
+        paths List(str): Folders to hash.
+
+    Returns:
+        str: sha256 hash that represents all the folders altogether
+    """
+    hashes = []
+
+    # The hash doesn't depend on the order of paths or folders, as the hashes get sorted after the fact
+    for path in paths:
+        for root, _, files in os.walk(path, topdown=False):
+            for file in files:
+                logging.debug(f"Hashing file {file}")
+                filepath = os.path.join(root, file)
+                hashes.append(get_file_hash(filepath))
+
+    hashes = sorted(hashes)
+    sha = hashlib.sha256()
+    for hash in hashes:
+        sha.update(hash.encode("utf-8"))
+    hash_val = sha.hexdigest()
+    logging.debug(f"Folder hash: {hash_val}")
+    return hash_val
+
+
+
+ +
+ + +
+ + + +

+get_uids(path) + +

+ + +
+ +

Retrieves the UID of all the elements in the specified path.

+ + + +

Returns:

+ + + + + + + + + + + + + +
TypeDescription
+ List[str] + +
+

List[str]: UIDs of objects in path.

+
+
+ +
+ Source code in cli/medperf/utils.py +
def get_uids(path: str) -> List[str]:
+    """Retrieves the UID of all the elements in the specified path.
+
+    Returns:
+        List[str]: UIDs of objects in path.
+    """
+    logging.debug("Retrieving datasets")
+    uids = next(os.walk(path))[1]
+    logging.debug(f"Found {len(uids)} datasets")
+    logging.debug(f"Datasets: {uids}")
+    return uids
+
+
+
+ +
+ + +
+ + + +

+pretty_error(msg) + +

+ + +
+ +

Prints an error message with typer protocol

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
msg + str + +
+

Error message to show to the user

+
+
+ required +
+ +
+ Source code in cli/medperf/utils.py +
def pretty_error(msg: str):
+    """Prints an error message with typer protocol
+
+    Args:
+        msg (str): Error message to show to the user
+    """
+    ui = config.ui
+    logging.warning(
+        "MedPerf had to stop execution. See logs above for more information"
+    )
+    if msg[-1] != ".":
+        msg = msg + "."
+    ui.print_error(msg)
+
+
+
+ +
+ + +
+ + + +

+remove_path(path) + +

+ + +
+ +

Cleans up a clutter object. In case of failure, it is moved to .trash

+ +
+ Source code in cli/medperf/utils.py +
51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
def remove_path(path):
+    """Cleans up a clutter object. In case of failure, it is moved to `.trash`"""
+
+    # NOTE: We assume medperf will always have permissions to unlink
+    # and rename clutter paths, since for now they are expected to live
+    # in folders owned by medperf
+
+    if not os.path.exists(path):
+        return
+    logging.info(f"Removing clutter path: {path}")
+
+    # Don't delete symlinks
+    if os.path.islink(path):
+        os.unlink(path)
+        return
+
+    try:
+        if os.path.isfile(path):
+            os.remove(path)
+        else:
+            shutil.rmtree(path)
+    except OSError as e:
+        logging.error(f"Could not remove {path}: {str(e)}")
+        move_to_trash(path)
+
+
+
+ +
+ + +
+ + + +

+sanitize_json(data) + +

+ + +
+ +

Makes sure the input data is JSON compliant.

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
data + dict + +
+

dictionary containing data to be represented as JSON.

+
+
+ required +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
dict + dict + +
+

sanitized dictionary

+
+
+ +
+ Source code in cli/medperf/utils.py +
def sanitize_json(data: dict) -> dict:
+    """Makes sure the input data is JSON compliant.
+
+    Args:
+        data (dict): dictionary containing data to be represented as JSON.
+
+    Returns:
+        dict: sanitized dictionary
+    """
+    json_string = json.dumps(data)
+    json_string = re.sub(r"\bNaN\b", '"nan"', json_string)
+    json_string = re.sub(r"(-?)\bInfinity\b", r'"\1Infinity"', json_string)
+    data = json.loads(json_string)
+    return data
+
+
+
+ +
+ + +
+ + + +

+untar(filepath, remove=True) + +

+ + +
+ +

Untars and optionally removes the tar.gz file

+ + + +

Parameters:

+ + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionDefault
filepath + str + +
+

Path where the tar.gz file can be found.

+
+
+ required +
remove + bool + +
+

Wether to delete the tar.gz file. Defaults to True.

+
+
+ True +
+ + + +

Returns:

+ + + + + + + + + + + + + +
Name TypeDescription
str + str + +
+

location where the untared files can be found.

+
+
+ +
+ Source code in cli/medperf/utils.py +
def untar(filepath: str, remove: bool = True) -> str:
+    """Untars and optionally removes the tar.gz file
+
+    Args:
+        filepath (str): Path where the tar.gz file can be found.
+        remove (bool): Wether to delete the tar.gz file. Defaults to True.
+
+    Returns:
+        str: location where the untared files can be found.
+    """
+    logging.info(f"Uncompressing tar.gz at {filepath}")
+    addpath = str(Path(filepath).parent)
+    tar = tarfile.open(filepath)
+    tar.extractall(addpath)
+    tar.close()
+
+    # OS Specific issue: Mac Creates superfluous files with tarfile library
+    [
+        remove_path(spurious_file)
+        for spurious_file in glob(addpath + "/**/._*", recursive=True)
+    ]
+    if remove:
+        logging.info(f"Deleting {filepath}")
+        remove_path(filepath)
+    return addpath
+
+
+
+ +
+ + + +
+ +
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..ab5ee1253 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +mkdocs==1.4.3 +mkdocs-autorefs==0.4.1 +mkdocs-gen-files==0.5.0 +mkdocs-literate-nav==0.6.0 +mkdocs-material==9.1.18 +mkdocs-material-extensions==1.1.1 +mkdocstrings==0.22.0 +mkdocstrings-python==1.2.0 +mkdocs-macros-plugin==1.0.2 +# For some reason, later versions of griffe are throwing recursion limit error +griffe==0.32.3 \ No newline at end of file diff --git a/roles/index.html b/roles/index.html new file mode 100644 index 000000000..880e2e9b0 --- /dev/null +++ b/roles/index.html @@ -0,0 +1,990 @@ + + + + + + + + + + + + + + + + + + + + + + + + Benchmark Roles - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

User Roles and Responsibilities

+

Here we introduce user roles at MedPerf. Depending on the objectives and expectations a user may have multiple roles.

+

Benchmark Committee

+

May include healthcare stakeholders (e.g., hospitals, clinicians, patient advocacy groups, payors, etc.), regulatory bodies, data providers and model owners wishing to drive the evaluation of AI models on real world data. While the Benchmark Committee does not have admin privileges on MedPerf, they have elevated permissions regarding benchmark assets (e.g., task, evaluation metrics, etc.) and policies (e.g., participation of model owners, data providers, anonymizations)

+

+

Data Providers

+

May include hospitals, medical practices, research organizations, and healthcare payors that own medical data, register medical data, and execute benchmarks.

+

+

Model Owners

+

May include ML researchers and software vendors that own a trained medical ML model and want to evaluate its performance against a benchmark.

+

+

Platform Providers

+

Organizations like MLCommons that operate the MedPerf platform enabling benchmark committees to develop and run benchmarks.

+

+ + + + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/script.js b/scripts/script.js new file mode 100644 index 000000000..e0db65f74 --- /dev/null +++ b/scripts/script.js @@ -0,0 +1,76 @@ + +function createSideImageContainer() { + const containerElement = document.createElement('div'); + containerElement.classList.add('side-container'); + + const imageElement = document.createElement('img'); + imageElement.src = ''; + imageElement.alt = ''; + + imageElement.setAttribute('class', 'tutorial-sticky-image'); + imageElement.setAttribute('id', 'tutorial-sticky-image'); + + containerElement.appendChild(imageElement); + + return containerElement; +} + +function imagesAppending(imageElement) { + let imageSrc = ''; + let smthWasSet = false; + + const contentImages = document.getElementsByClassName("tutorial-sticky-image-content") + for (let contentImageElement of contentImages) { + + const rect = contentImageElement.getBoundingClientRect(); + if (300 > rect.top) { + imageSrc = contentImageElement.src; + smthWasSet = true; + console.log(rect.top, "changing image to", imageSrc) + if (imageElement) { + imageElement.src = imageSrc; + imageElement.style.display="block"; + } + } + } + if (!smthWasSet) { + console.log("no image was chosen. Hid the image"); + imageElement.style.display="none"; + } +} + +// ADDS IMAGING SCROLL TO TUTORIALS +window.addEventListener('scroll', function() { + let containerElement = document.querySelector('.side-container'); + + if (containerElement) { + const imageElement = containerElement.querySelector('img'); + imagesAppending(imageElement); + } +}) + +// ADDS HOME BUTTON TO HEADER +document.addEventListener('DOMContentLoaded', function() { + const headerTitle = document.querySelector(".md-header__ellipsis"); + + if (headerTitle && window.location.pathname !== "/") { + const newElement = document.createElement("a"); + newElement.textContent = "Home"; + newElement.classList.add('header_home_btn'); + newElement.href = "/"; + + headerTitle.appendChild(newElement); + console.log(document.querySelector(".header_home_btn")) + } + + const contentImages = document.getElementsByClassName("tutorial-sticky-image-content"); + if (contentImages.length) { + console.log('content images found: ', contentImages) + // we add an element only if there are content images on this page to be shown + const tutorialStickyImageElement = createSideImageContainer(); + const contentElement = document.getElementsByClassName('md-content')[0]; + contentElement.parentNode.insertBefore(tutorialStickyImageElement, contentElement.nextSibling); + imagesAppending(tutorialStickyImageElement.querySelector('img')); + } + +}); \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 000000000..eee0ff750 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"cli_reference/","title":"In Progress","text":"

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

"},{"location":"medperf_components/","title":"MedPerf Components","text":""},{"location":"medperf_components/#medperf-server","title":"MedPerf Server","text":"

The server contains all the metadata necessary to coordinate and execute experiments. No code assets or datasets are stored on the server.

The backend server is implemented in Django, and it can be found in the server folder in the MedPerf Github repository.

"},{"location":"medperf_components/#medperf-client","title":"MedPerf Client","text":"

The MedPerf client contains all the necessary tools to interact with the server, preparing datasets for benchmarks and running experiments on the local machine. It can be found in this folder in the MedPerf Github repository.

The client communicates to the server through the API to, for example, authenticate a user, retrieve benchmarks/MLcubes and send results.

The client is currently available to the user through a command-line interface (CLI).

"},{"location":"medperf_components/#auth-provider","title":"Auth Provider","text":"

The auth provider manages MedPerf users identities, authentication, and authorization to access the MedPerf server. Users will authenticate with the auth provider and authorize their MedPerf client to access the MedPerf server. Upon authorization, the MedPerf client will use access tokens issued by the auth provider in every request to the MedPerf server. The MedPerf server is configured to processes only requests authorized by the auth provider.

Currently, MedPerf uses Auth0 as the auth provider.

"},{"location":"roles/","title":"User Roles and Responsibilities","text":"

Here we introduce user roles at MedPerf. Depending on the objectives and expectations a user may have multiple roles.

"},{"location":"roles/#benchmark-committee","title":"Benchmark Committee","text":"

May include healthcare stakeholders (e.g., hospitals, clinicians, patient advocacy groups, payors, etc.), regulatory bodies, data providers and model owners wishing to drive the evaluation of AI models on real world data. While the Benchmark Committee does not have admin privileges on MedPerf, they have elevated permissions regarding benchmark assets (e.g., task, evaluation metrics, etc.) and policies (e.g., participation of model owners, data providers, anonymizations)

"},{"location":"roles/#data-providers","title":"Data Providers","text":"

May include hospitals, medical practices, research organizations, and healthcare payors that own medical data, register medical data, and execute benchmarks.

"},{"location":"roles/#model-owners","title":"Model Owners","text":"

May include ML researchers and software vendors that own a trained medical ML model and want to evaluate its performance against a benchmark.

"},{"location":"roles/#platform-providers","title":"Platform Providers","text":"

Organizations like MLCommons that operate the MedPerf platform enabling benchmark committees to develop and run benchmarks.

"},{"location":"what_is_medperf/","title":"What is Medperf?","text":"

MedPerf is an open-source framework for benchmarking medical ML models. It uses Federated Evaluation a method in which medical ML models are securely distributed to multiple global facilities for evaluation prioritizing patient privacy to mitigate legal and regulatory risks. The goal of Federated Evaluation is to make it simple and reliable to share ML models with many data providers, evaluate those ML models against their data in controlled settings, then aggregate and analyze the findings.

The MedPerf approach empowers healthcare stakeholders through neutral governance to assess and verify the performance of ML models in an efficient and human-supervised process without sharing any patient data across facilities during the process.

Federated evaluation of medical AI model using MedPerf on a hypothetical example"},{"location":"what_is_medperf/#why-medperf","title":"Why MedPerf?","text":"

MedPerf aims to identify bias and generalizability issues of medical ML models by evaluating them on diverse medical data across the world. This process allows developers of medical ML to efficiently identify performance and reliability issues on their models while healthcare stakeholders (e.g., hospitals, practices, etc.) can validate such models against clinical efficacy.

Importantly, MedPerf supports technology for neutral governance in order to enable full trust and transparency among participating parties (e.g., AI vendor, data provider, regulatory body, etc.). This is all encapsulated in the benchmark committee which is the overseeing body on a benchmark.

Benchmark committee in MedPerf"},{"location":"what_is_medperf/#benefits-to-healthcare-stakeholders","title":"Benefits to healthcare stakeholders","text":"

Anyone who joins our platform can get several benefits, regardless of the role they will assume.

Benefits to healthacare stakeholders using MedPerf

Our paper describes the design philosophy in detail.

"},{"location":"workflow/","title":"Benchmark Workflow","text":"

A benchmark in MedPerf is a collection of assets that are developed by the benchmark committee that aims to evaluate medical ML on decentralized data providers.

The process is simple yet effective enabling scalability.

"},{"location":"workflow/#step-1-establish-benchmark-committee","title":"Step 1. Establish Benchmark Committee","text":"

The benchmarking process starts with establishing a benchmark committee of healthcare stakeholders (experts, committee), which will identify a clinical problem where an effective ML-based solution can have a significant clinical impact.

"},{"location":"workflow/#step-2-register-benchmark","title":"Step 2. Register Benchmark","text":"

MLCubes are the building blocks of an experiment and are required in order to create a benchmark. Three MLCubes (Data Preparator MLCube, Reference Model MLCube, and Metrics MLCube) need to be submitted. After submitting the three MLCubes, alongside with a sample reference dataset, the Benchmark Committee is capable of creating a benchmark. Once the benchmark is submitted, the Medperf admin must approve it before it can be seen by other users. Follow our Hands-on Tutorial for detailed step-by-step guidelines.

"},{"location":"workflow/#step-3-register-dataset","title":"Step 3. Register Dataset","text":"

Data Providers that want to be part of the benchmark can register their own datasets, prepare them, and associate them with the benchmark. A dataset will be prepared using the benchmark's Data Preparator MLCube and the dataset's metadata is registered within the MedPerf server.

Data Preparation

The data provider then can request to participate in the benchmark with their dataset. Requesting the association will run the benchmark's reference workflow to assure the compatibility of the prepared dataset structure with the workflow. Once the association request is approved by the Benchmark Committee, then the dataset becomes a part of the benchmark.

"},{"location":"workflow/#step-4-register-models","title":"Step 4. Register Models","text":"

Once a benchmark is submitted by the Benchmark Committee, any user can submit their own Model MLCubes and request an association with the benchmark. This association request executes the benchmark locally with the given model on the benchmark's reference dataset to ensure workflow validity and compatibility. If the model successfully passes the compatibility test, and its association is approved by the Benchmark Committee, it becomes a part of the benchmark.

"},{"location":"workflow/#step-5-execute-benchmark","title":"Step 5. Execute Benchmark","text":"

The Benchmark Committee may notify Data Providers that models are available for benchmarking. Data Providers can then run the benchmark models locally on their data.

This procedure retrieves the model MLCubes associated with the benchmark and runs them on the indicated prepared dataset to generate predictions. The Metrics MLCube of the benchmark is then retrieved to evaluate the predictions. Once the evaluation results are generated, the data provider can submit them to the platform.

"},{"location":"workflow/#step-6-aggregate-and-release-results","title":"Step 6. Aggregate and Release Results","text":"

The benchmarking platform aggregates the results of running the models against the datasets and shares them according to the Benchmark Committee's policy.

The sharing policy controls how much of the data is shared, ranging from a single aggregated metric to a more detailed model-data cross product. A public leaderboard is available to Model Owners who produce the best performances.

"},{"location":"concepts/associations/","title":"In Progress","text":"

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

"},{"location":"concepts/auth/","title":"Authentication","text":"

This guide helps you learn how to login and logout using the MedPerf client to access the main production MedPerf server. MedPerf uses passwordless authentication. This means that login will only require you to access your email in order complete the login process.

"},{"location":"concepts/auth/#login","title":"Login","text":"

Follow the steps below to login:

  • Step1 Run the following command:
medperf auth login\n

You will be prompted to enter your email address.

After entering your email address, you will be provided with a verification URL and a code. A text similar to the following will be printed in your terminal:

Tip

If you are running the MedPerf client on a machine with no graphical interface, you can use the link on any other device, e.g. your cellphone. Make sure that you trust that device.

  • Step2 Open the verification URL and confirm the code:

Open the printed URL in your browser. You will be presented with a code, and you will be asked to confirm if that code is the same one printed in your terminal.

  • Step3 After confirmation, you will be asked to enter your email address. Enter your email address and press \"Continue\". You will see the following screen:

  • Step4 Check your inbox. You should receive an email similar to the following:

Enter the received code in the previous screen.

  • Step5 If there is no problem with your account, the login will be successful, and you will see a screen similar to the following:

"},{"location":"concepts/auth/#logout","title":"Logout","text":"

To disconnect the MedPerf client, simply run the following command:

medperf auth logout\n
"},{"location":"concepts/auth/#checking-the-authentication-status","title":"Checking the authentication status","text":"

Note that when you log in, the MedPerf client will remember you as long as you are using the same profile. If you switch to another profile by running medperf profile activate <other-profile>, you may have to log in again. If you switch back again to a profile where you previously logged in, your login state will be restored.

You can always check the current login status by the running the following command:

medperf auth status\n
"},{"location":"concepts/hosting_files/","title":"Hosting Files","text":"

MedPerf requires some files to be hosted on the cloud when running machine learning pipelines. Submitting MLCubes to the MedPerf server means submitting their metadata, and not, for example, model weights or parameters files. MLCube files such as model weights need to be hosted on the cloud, and the submitted MLCube metadata will only contain URLs (or certain identifiers) for these files. Another example would be benchmark submission, where demo datasets need to be hosted.

The MedPerf client expects files to be hosted in certain ways. Below are options of how files can be hosted and how MedPerf identitfies them (e.g. a URL).

"},{"location":"concepts/hosting_files/#file-hosting","title":"File hosting","text":"

This can be done with any cloud hosting tool/provider you desire (such as GCP, AWS, Dropbox, Google Drive, Github). As long as your file can be accessed through a direct download link, it should work with medperf. Generating a direct download link for your hosted file can be straight-forward when using some providers (e.g. Amazon Web Services, Google Cloud Platform, Microsoft Azure) and can be a bit tricky when using others (e.g. Dropbox, GitHub, Google Drive).

Note

Direct download links must be permanent

Tip

You can make sure if a URL is a direct download link or not using tools like wget or curl. Running wget <URL> will download the file if the URL is a direct download link. Running wget <URL> may fail or may download an HTML page if the URL is not a direct download link.

When your file is hosted with a direct download link, MedPerf will be able to identify this file using that direct download link. So for example, when you are submitting an MLCube, you would pass your hosted MLCube manifest file as follows:

--mlcube-file <the-direct-download-link-to-the-file>\n

Warning

Files in this case are supposed to have anonymous public read access permission.

"},{"location":"concepts/hosting_files/#direct-download-links-of-files-on-github","title":"Direct download links of files on GitHub","text":"

It was a common practice by the current MedPerf users to host files on GitHub. You can learn below how to find the direct download link of a file hosted on GitHub. You can check online for other storage providers.

It's important though to make sure the files won't be modified after being submitted to medperf, which could happen due to future commits. Because of this, the URLs of the files hosted on GitHub must contain a reference to the current commit hash. Below are the steps to get this URL for a specific file:

  1. Open the GitHub repository and ensure you are in the correct branch
  2. Click on \u201cCommits\u201d at the right top corner of the repository explorer.
  3. Locate the latest commit, it is the top most commit.
  4. If you are targeting previous versions of your file, make sure to consider the right commit.
  5. Click on this button \u201c<>\u201d corresponding to the commit (Browse the repository at this point in the history).
  6. Navigate to the file of interest.
  7. Click on \u201cRaw\u201d.
  8. Copy the url from your browser. It should be a UserContent GitHub URLs (domain raw.githubusercontent.com).
"},{"location":"concepts/hosting_files/#synapse-hosting","title":"Synapse hosting","text":"

You can choose the option of hosting with Synapse in cases where privacy is a concern. Please refer to this link for hosting files on the Synapse platform.

When your file is hosted on Synapse, MedPerf will be able to identify this file using the Synapse ID corresponding to that file. So for example, when you are submitting an MLCube, you would pass your hosted MLCube manifest file as follows (note the prefix):

--mlcube-file synapse:<the-synapse-id-of-the-file>\n

Note that you need to authenticate with your Synapse credentials if you plan to use a Synaspe file with MedPerf. To do so run medperf auth synapse_login.

Note

You must authenticate if using files on Synapse. If this is not necessary, this means the file has anonymous public access read permission. In this case, Synapse allows you to generate a permanent direct download link for your file and you can follow the previous section.

"},{"location":"concepts/mlcube_files/","title":"MLCube Components: What to Host?","text":"

Once you have built an MLCube ready for MedPerf, you need to host it somewhere on the cloud so that it can be identified and retrieved by the MedPerf client on other machines. This requires hosting the MLCube components somewhere on the cloud. The following is a description of what needs to be hosted.

"},{"location":"concepts/mlcube_files/#hosting-your-container-image","title":"Hosting Your Container Image","text":"

MLCubes execute a container image behind the scenes. This container image is usually hosted on a container registry, like Docker Hub. In cases where this is not possible, medperf provides the option of passing the image file directly (i.e. having the image file hosted somewhere and providing MedPerf with the download link). MLCubes that work with images outside of the docker registry usually store the image inside the <path_to_mlcube>/workspace/.image folder. MedPerf supports using direct container image files for Singularity only.

Note

While there is the option of hosting the singularity image directly, it is highly recommended to use a container registry for accessability and usability purposes. MLCube also has mechanisms for converting containers to other runners, like Docker to Singularity.

Note

Docker Images can be on any docker container registry, not necessarily on Docker Hub.

"},{"location":"concepts/mlcube_files/#files-to-be-hosted","title":"Files to be hosted","text":"

The following is the list of files that must be hosted separately so they can be used by MedPerf:

"},{"location":"concepts/mlcube_files/#mlcubeyaml","title":"mlcube.yaml","text":"

Every MLCube is defined by its mlcube.yaml manifest file. As such, Medperf needs to have access to this file to recreate the MLCube. This file can be found inside your MLCube at <path_to_mlcube>/mlcube.yaml.

"},{"location":"concepts/mlcube_files/#parametersyaml-optional","title":"parameters.yaml (Optional)","text":"

The parameters.yaml file specify additional ways to parametrize your model MLCube using the same container image it is built with. This file can be found inside your MLCube at <path_to_mlcube>/workspace/parameters.yaml.

"},{"location":"concepts/mlcube_files/#additional_filestargz-optional","title":"additional_files.tar.gz (Optional)","text":"

MLCubes may require additional files that may be desired to keep separate from the model architecture and hosted image. For example, model weights. This allows for testing multiple implementations of the same model, without requiring a separate container image for each. If additional images are being used by your MLCube, they need to be compressed into a .tar.gz file and hosted separately. You can create this tarball file with the following command

tar -czf additional_files.tar.gz -C <path_to_mlcube>/workspace/additional_files .\n
"},{"location":"concepts/mlcube_files/#preparing-an-mlcube-for-hosting","title":"Preparing an MLCube for hosting","text":"

To facilitate hosting and interface compatibility validation, MedPerf provides a script that finds all the required assets, compresses them if necessary, and places them in a single location for easy access. To run the script, make sure you have medperf installed and you are in medperf's root directory:

python scripts/package-mlcube.py \\\n--mlcube path/to/mlcube \\\n--mlcube-types <list-of-comma-separated-strings> \\\n--output path/to/file.tar.gz\n

where:

  • path/to/mlcube is the path to the MLCube folder containing the manifest file (mlcube.yaml)
  • --mlcube-types specifies a comma-separated list of MLCube types ('data-preparator' for a data preparation MLCube, 'model' for a model MLCube, and 'metrics' for a metrics MLCube.)
  • path/to/file.tar.gz is a path to the output file where you want to store the compressed version of all assets.

See python scripts/package-mlcube.py --help for more information.

Once executed, you should be able to find all prepared assets at ./mlcube/assets, as well as a compressed version of the assets folder at the output path provided.

Note

The --output parameter is optional. The compressed version of the assets folder can be useful in cases where you don't directly interact with the MedPerf server, but instead you do so through a third party. This is usually the case for challenges and competitions.

"},{"location":"concepts/mlcube_files/#see-also","title":"See Also","text":"
  • File Hosting
"},{"location":"concepts/priorities/","title":"In Progress","text":"

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

"},{"location":"concepts/profiles/","title":"In Progress","text":"

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

"},{"location":"concepts/single_run/","title":"In Progress","text":"

TODO: the page is hidden now. If implemented, find all usages and uncomment them.

"},{"location":"getting_started/benchmark_owner_demo/","title":"Bechmark Committee","text":""},{"location":"getting_started/benchmark_owner_demo/#hands-on-tutorial-for-bechmark-committee","title":"Hands-on Tutorial for Bechmark Committee","text":""},{"location":"getting_started/benchmark_owner_demo/#overview","title":"Overview","text":"

In this guide, you will learn how a user can use MedPerf to create a benchmark. The key tasks can be summarized as follows:

  1. Implement a valid workflow.
  2. Develop a demo dataset.
  3. Test your workflow.
  4. Submitting the MLCubes to the MedPerf server.
  5. Host the demo dataset.
  6. Submit the benchmark to the MedPerf server.

It's assumed that you have already set up the general testing environment as explained in the installation and setup guide.

"},{"location":"getting_started/benchmark_owner_demo/#before-you-start","title":"Before You Start","text":""},{"location":"getting_started/benchmark_owner_demo/#first-steps","title":"First steps","text":""},{"location":"getting_started/benchmark_owner_demo/#running-in-cloud-via-github-codespaces","title":"Running in cloud via Github Codespaces","text":"

As the most easy way to play with the tutorials you can launch a preinstalled Codespace cloud environment for MedPerf by clicking this link:

"},{"location":"getting_started/benchmark_owner_demo/#running-in-local-environment","title":"Running in local environment","text":"

To start experimenting with MedPerf through this tutorial on your local machine, you need to start by following these quick steps:

  1. Install Medperf
  2. Set up Medperf
"},{"location":"getting_started/benchmark_owner_demo/#prepare-the-local-medperf-server","title":"Prepare the Local MedPerf Server","text":"

For the purpose of the tutorial, you have to initialize a local MedPerf server with a fresh database and then create the necessary entities that you will be interacting with. To do so, run the following: (make sure you are in MedPerf's root folder)

cd server\nsh reset_db.sh\npython seed.py --demo benchmark\ncd ..\n
"},{"location":"getting_started/benchmark_owner_demo/#download-the-necessary-files","title":"Download the Necessary files","text":"

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

sh tutorials_scripts/setup_benchmark_tutorial.sh\n

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. The folder contains the following content:

Toy content description

In this tutorial we will create a benchmark that classifies chest X-Ray images.

In real life all the listed artifacts and files have to be created on your own. However, for tutorial's sake you may use this toy data.

"},{"location":"getting_started/benchmark_owner_demo/#demo-data","title":"Demo Data","text":"

The medperf_tutorial/demo_data/ folder contains the demo dataset content.

  • images/ folder includes sample images.
  • labels/labels.csv provides a basic ground truth markup, indicating the class each image belongs to.

The demo dataset is a sample dataset used for the development of your benchmark and used by Model Owners for the development of their models. More details are available in the section below

"},{"location":"getting_started/benchmark_owner_demo/#data-preparator-mlcube","title":"Data Preparator MLCube","text":"

The medperf_tutorial/data_preparator/ contains a DataPreparator MLCube that you must implement. This MLCube: - Transforms raw data into a format convenient for model consumption, such as converting DICOM images into numpy tensors, cropping patches, normalizing columns, etc. It's up to you to define the format that is handy for future models. - Ensures its output is in a standardized format, allowing Model Owners/Developers to rely on its consistency.

"},{"location":"getting_started/benchmark_owner_demo/#model-mlcube","title":"Model MLCube","text":"

The medperf_tutorial/model_custom_cnn/ is an example of a Model MLCube. You need to implement a reference model which will be used by data owners to test the compatibility of their data with your pipeline. Also, Model Developers joining your benchmark will follow the input/output specifications of this model when building their own models.

"},{"location":"getting_started/benchmark_owner_demo/#metrics-mlcube","title":"Metrics MLCube","text":"

The medperf_tutorial/metrics/ houses a Metrics MLCube that processes ground truth data, model predictions, and computes performance metrics - such as classification accuracy, loss, etc. After a Dataset Owner runs the benchmark pipeline on their data, these final metric values will be shared with you as the Benchmark Owner.

"},{"location":"getting_started/benchmark_owner_demo/#login-to-the-local-medperf-server","title":"Login to the Local MedPerf Server","text":"

The local MedPerf server is pre-configured with a dummy local authentication system. Remember that when you are communicating with the real MedPerf server, you should follow the steps in this guide to login. For the tutorials, you should not do anything.

You are now ready to start!

"},{"location":"getting_started/benchmark_owner_demo/#1-implement-a-valid-workflow","title":"1. Implement a Valid Workflow","text":"

The implementation of a valid workflow is accomplished by implementing three MLCubes:

  1. Data Preparator MLCube: This MLCube will transform raw data into a dataset ready for the AI model execution. All data owners willing to participate in this benchmark will have their data prepared using this MLCube. A guide on how to implement data preparation MLCubes can be found here.

  2. Reference Model MLCube: This MLCube will contain an example model implementation for the desired AI task. It should be compatible with the data preparation MLCube (i.e., the outputs of the data preparation MLCube can be directly fed as inputs to this MLCube). A guide on how to implement model MLCubes can be found here.

  3. Metrics MLCube: This MLCube will be responsible for evaluating the performance of a model. It should be compatible with the reference model MLCube (i.e., the outputs of the reference model MLCube can be directly fed as inputs to this MLCube). A guide on how to implement metrics MLCubes can be found here.

For this tutorial, you are provided with following three already implemented mlcubes for the task of chest X-ray classification. The implementations can be found in the following links: Data Preparator, Reference Model, Metrics. These mlcubes are setup locally for you and can be found in your workspace folder under data_preparator, model_custom_cnn, and metrics.

"},{"location":"getting_started/benchmark_owner_demo/#2-develop-a-demo-dataset","title":"2. Develop a Demo Dataset","text":"

A demo dataset is a small reference dataset. It contains a few data records and their labels, which will be used to test the benchmark's workflow in two scenarios:

  1. It is used for testing the benchmark's default workflow. The MedPerf client automatically runs a compatibility test of the benchmark's three mlcubes prior to its submission. The test is run using the benchmark's demo dataset as input.

  2. When a model owner wants to participate in the benchmark, the MedPerf client tests the compatibility of their model with the benchmark's data preparation cube and metrics cube. The test is run using the benchmark's demo dataset as input.

For this tutorial, you are provided with a demo dataset for the chest X-ray classification workflow. The dataset can be found in your workspace folder under demo_data. It is a small dataset comprising two chest X-ray images and corresponding thoracic disease labels.

You can test the workflow now that you have the three MLCubes and the demo data. Testing the workflow before submitting any asset to the MedPerf server is usually recommended.

"},{"location":"getting_started/benchmark_owner_demo/#3-test-your-workflow","title":"3. Test your Workflow","text":"

MedPerf provides a single command to test an inference workflow. To test your workflow with local MLCubes and local data, the following need to be passed to the command:

  1. Path to the data preparation MLCube manifest file: medperf_tutorial/data_preparator/mlcube/mlcube.yaml.
  2. Path to the model MLCube manifest file: medperf_tutorial/model_custom_cnn/mlcube/mlcube.yaml.
  3. Path to the metrics MLCube manifest file: medperf_tutorial/metrics/mlcube/mlcube.yaml.
  4. Path to the demo dataset data records: medperf_tutorial/demo_data/images.
  5. Path to the demo dataset data labels. medperf_tutorial/demo_data/labels.

Run the following command to execute the test ensuring you are in MedPerf's root folder:

medperf test run \\\n--data_preparation \"medperf_tutorial/data_preparator/mlcube/mlcube.yaml\" \\\n--model \"medperf_tutorial/model_custom_cnn/mlcube/mlcube.yaml\" \\\n--evaluator \"medperf_tutorial/metrics/mlcube/mlcube.yaml\" \\\n--data_path \"medperf_tutorial/demo_data/images\" \\\n--labels_path \"medperf_tutorial/demo_data/labels\"\n

Assuming the test passes successfully, you are ready to submit the MLCubes to the MedPerf server.

"},{"location":"getting_started/benchmark_owner_demo/#4-host-the-demo-dataset","title":"4. Host the Demo Dataset","text":"

The demo dataset should be packaged in a specific way as a compressed tarball file. The folder stucture in the workspace currently looks like the following:

.\n\u2514\u2500\u2500 medperf_tutorial\n    \u251c\u2500\u2500 demo_data\n    \u2502   \u251c\u2500\u2500 images\n    \u2502   \u2514\u2500\u2500 labels\n    \u2502\n    ...\n

The goal is to package the folder demo_data. You must first create a file called paths.yaml. This file will provide instructions on how to locate the data records path and the labels path. The paths.yaml file should specify both the data records path and the labels path.

In your workspace directory (medperf_tutorial), create a file paths.yaml and fill it with the following:

data_path: demo_data/images\nlabels_path: demo_data/labels\n

Note

The paths are determined by the Data Preparator MLCube's expected input path.

After that, the workspace should look like the following:

.\n\u2514\u2500\u2500 medperf_tutorial\n    \u251c\u2500\u2500 demo_data\n    \u2502   \u251c\u2500\u2500 images\n    \u2502   \u2514\u2500\u2500 labels\n    \u251c\u2500\u2500 paths.yaml\n    \u2502\n    ...\n

Finally, compress the required assets (demo_data and paths.yaml) into a tarball file by running the following command:

cd medperf_tutorial\ntar -czf demo_data.tar.gz demo_data paths.yaml\ncd ..\n

And that's it! Now you have to host the tarball file (demo_data.tar.gz) on the internet.

For the tutorial to run smoothly, the file is already hosted at the following URL:

https://storage.googleapis.com/medperf-storage/chestxray_tutorial/demo_data.tar.gz\n

If you wish to host it by yourself, you can find the list of supported options and details about hosting files in this page.

Finally, now after having the MLCubes submitted and the demo dataset hosted, you can submit the benchmark to the MedPerf server.

"},{"location":"getting_started/benchmark_owner_demo/#5-submitting-the-mlcubes","title":"5. Submitting the MLCubes","text":""},{"location":"getting_started/benchmark_owner_demo/#how-does-medperf-recognize-an-mlcube","title":"How does MedPerf Recognize an MLCube?","text":"

The MedPerf server registers an MLCube as metadata comprised of a set of files that can be retrieved from the internet. This means that before submitting an MLCube you have to host its files on the internet. The MedPerf client provides a utility to prepare the files of an MLCube that need to be hosted. You can refer to this page if you want to understand what the files are, but using the utility script is enough.

To prepare the files of the three MLCubes, run the following command ensuring you are in MedPerf's root folder:

python scripts/package-mlcube.py --mlcube medperf_tutorial/data_preparator/mlcube --mlcube-types data-preparator\npython scripts/package-mlcube.py --mlcube medperf_tutorial/model_custom_cnn/mlcube --mlcube-types model\npython scripts/package-mlcube.py --mlcube medperf_tutorial/metrics/mlcube --mlcube-types metrics\n

For each MLCube, this script will create a new folder named assets in the MLCube directory. This folder will contain all the files that should be hosted separately.

"},{"location":"getting_started/benchmark_owner_demo/#host-the-files","title":"Host the Files","text":"

For the tutorial to run smoothly, the files are already hosted. If you wish to host them by yourself, you can find the list of supported options and details about hosting files in this page.

"},{"location":"getting_started/benchmark_owner_demo/#submit-the-mlcubes","title":"Submit the MLCubes","text":""},{"location":"getting_started/benchmark_owner_demo/#data-preparator-mlcube_1","title":"Data Preparator MLCube","text":"

For the Data Preparator MLCube, the submission should include:

  • The URL to the hosted mlcube manifest file, which is:

    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/mlcube.yaml\n
  • The URL to the hosted mlcube parameters file, which is:

    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/workspace/parameters.yaml\n

Use the following command to submit:

medperf mlcube submit \\\n--name my-prep-cube \\\n--mlcube-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/mlcube.yaml\" \\\n--parameters-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/data_preparator/mlcube/workspace/parameters.yaml\" \\\n--operational\n
"},{"location":"getting_started/benchmark_owner_demo/#reference-model-mlcube","title":"Reference Model MLCube","text":"

For the Reference Model MLCube, the submission should include:

  • The URL to the hosted mlcube manifest file:

    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/mlcube.yaml\n
  • The URL to the hosted mlcube parameters file:

    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/workspace/parameters.yaml\n
  • The URL to the hosted additional files tarball file:

    https://storage.googleapis.com/medperf-storage/chestxray_tutorial/cnn_weights.tar.gz\n

Use the following command to submit:

medperf mlcube submit \\\n--name my-modelref-cube \\\n--mlcube-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/mlcube.yaml\" \\\n--parameters-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_custom_cnn/mlcube/workspace/parameters.yaml\" \\\n--additional-file \"https://storage.googleapis.com/medperf-storage/chestxray_tutorial/cnn_weights.tar.gz\" \\\n--operational\n
"},{"location":"getting_started/benchmark_owner_demo/#metrics-mlcube_1","title":"Metrics MLCube","text":"

For the Metrics MLCube, the submission should include:

  • The URL to the hosted mlcube manifest file:

    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/mlcube.yaml\n
  • The URL to the hosted mlcube parameters file:

    https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/workspace/parameters.yaml\n

Use the following command to submit:

medperf mlcube submit \\\n--name my-metrics-cube \\\n--mlcube-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/mlcube.yaml\" \\\n--parameters-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/metrics/mlcube/workspace/parameters.yaml\" \\\n--operational\n

Each of the three MLCubes will be assigned by a server UID. You can check the server UID for each MLCube by running:

medperf mlcube ls --mine\n

Next, you will learn how to host the demo dataset.

"},{"location":"getting_started/benchmark_owner_demo/#6-submit-your-benchmark","title":"6. Submit your Benchmark","text":"

You need to keep at hand the following information:

  • The Demo Dataset URL. Here, the URL will be:
https://storage.googleapis.com/medperf-storage/chestxray_tutorial/demo_data.tar.gz\n
  • The server UIDs of the three MLCubes can be found by running:
 medperf mlcube ls\n
  • For this tutorial, the UIDs are as follows:
  • Data preparator UID: 1
  • Reference model UID: 2
  • Evaluator UID: 3

You can create and submit your benchmark using the following command:

medperf benchmark submit \\\n--name tutorial_bmk \\\n--description \"MedPerf demo bmk\" \\\n--demo-url \"https://storage.googleapis.com/medperf-storage/chestxray_tutorial/demo_data.tar.gz\" \\\n--data-preparation-mlcube 1 \\\n--reference-model-mlcube 2 \\\n--evaluator-mlcube 3 \\\n--operational\n

The MedPerf client will first automatically run a compatibility test between the MLCubes using the demo dataset. If the test is successful, the benchmark will be submitted along with the compatibility test results.

Note

The benchmark will stay inactive until the MedPerf server admin approves your submission.

That's it! You can check your benchmark's server UID by running:

medperf benchmark ls --mine\n

"},{"location":"getting_started/benchmark_owner_demo/#cleanup-optional","title":"Cleanup (Optional)","text":"

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

  • To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

  • To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

rm -fr medperf_tutorial\n
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
cd server\nsh reset_db.sh\n
  • To cleanup the test storage:
rm -fr ~/.medperf/localhost_8000\n
"},{"location":"getting_started/data_owner_demo/","title":"Data Owners","text":""},{"location":"getting_started/data_owner_demo/#hands-on-tutorial-for-data-owners","title":"Hands-on Tutorial for Data Owners","text":""},{"location":"getting_started/data_owner_demo/#overview","title":"Overview","text":"

As a data owner, you plan to run a benchmark on your own dataset. Using MedPerf, you will prepare your (raw) dataset and submit information about it to the MedPerf server. You may have to consult the benchmark committee to make sure that your raw dataset aligns with the benchmark's expected input format.

Note

A key concept of MedPerf is the stringent confidentiality of your data. It remains exclusively on your machine. Only minimal information about your dataset, such as the hash of its contents, is submitted. Once your Dataset is submitted and associated with a benchmark, you can run all benchmark models on your data within your own infrastructure and see the results / predictions.

This guide provides you with the necessary steps to use MedPerf as a Data Owner. The key tasks can be summarized as follows:

  1. Register your data information.
  2. Prepare your data.
  3. Mark your data as operational.
  4. Request participation in a benchmark.
  5. Execute the benchmark models on your dataset.
  6. Submit a result.

It is assumed that you have the general testing environment set up.

"},{"location":"getting_started/data_owner_demo/#before-you-start","title":"Before You Start","text":""},{"location":"getting_started/data_owner_demo/#first-steps","title":"First steps","text":""},{"location":"getting_started/data_owner_demo/#running-in-cloud-via-github-codespaces","title":"Running in cloud via Github Codespaces","text":"

As the most easy way to play with the tutorials you can launch a preinstalled Codespace cloud environment for MedPerf by clicking this link:

"},{"location":"getting_started/data_owner_demo/#running-in-local-environment","title":"Running in local environment","text":"

To start experimenting with MedPerf through this tutorial on your local machine, you need to start by following these quick steps:

  1. Install Medperf
  2. Set up Medperf
"},{"location":"getting_started/data_owner_demo/#prepare-the-local-medperf-server","title":"Prepare the Local MedPerf Server","text":"

For the purpose of the tutorial, you have to initialize a local MedPerf server with a fresh database and then create the necessary entities that you will be interacting with. To do so, run the following: (make sure you are in MedPerf's root folder)

cd server\nsh reset_db.sh\npython seed.py --demo data\ncd ..\n
"},{"location":"getting_started/data_owner_demo/#download-the-necessary-files","title":"Download the Necessary files","text":"

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

sh tutorials_scripts/setup_data_tutorial.sh\n

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. The folder contains the following content:

Toy content description

In real life all the listed artifacts and files have to be created on your own. However, for tutorial's sake you may use this toy data.

"},{"location":"getting_started/data_owner_demo/#tutorials-dataset-example","title":"Tutorial's Dataset Example","text":"

The medperf_tutorial/sample_raw_data/ folder contains your data for the specified Benchmark. In this tutorial, where the benchmark involves classifying chest X-Ray images, your data comprises:

  • images/ folder contains your images
  • labels/labels.csv, which provides the ground truth markup, specifying the class of each image.

The format of this data is dictated by the Benchmark Owner, as it must be compatible with the benchmark's Data Preparation MLCube. In a real-world scenario, the expected data format would differ from this toy example. Refer to the Benchmark Owner to get a format specifications and details for your practical case.

As previously mentioned, your data itself never leaves your machine. During the dataset submission, only basic metadata is transferred, for which you will be prompted to confirm.

"},{"location":"getting_started/data_owner_demo/#login-to-the-local-medperf-server","title":"Login to the Local MedPerf Server","text":"

The local MedPerf server is pre-configured with a dummy local authentication system. Remember that when you are communicating with the real MedPerf server, you should follow the steps in this guide to login. For the tutorials, you should not do anything.

You are now ready to start!

"},{"location":"getting_started/data_owner_demo/#1-register-your-data-information","title":"1. Register your Data Information","text":"

To register your dataset, you need to collect the following information:

  • A name you wish to have for your dataset.
  • A small description of the dataset.
  • The source location of your data (e.g., hospital name).
  • The path to the data records (here, it is medperf_tutorial/sample_raw_data/images).
  • The path to the labels of the data (here, it is medperf_tutorial/sample_raw_data/labels)
  • The benchmark ID that you wish to participate in. This ensures your data in the next step will be prepared using the benchmark's data preparation MLCube.

Note

The data_path and labels_path are determined according to the input path requirements of the data preparation MLCube. To ensure that your data is structured correctly, it is recommended to check with the Benchmark Committee for specific details or instructions.

In order to find the benchmark ID, you can execute the following command to view the list of available benchmarks.

medperf benchmark ls\n

The target benchmark ID here is 1.

Note

You will be submitting general information about the data, not the data itself. The data never leaves your machine.

Run the following command to register your data (make sure you are in MedPerf's root folder):

medperf dataset submit \\\n--name \"mytestdata\" \\\n--description \"A tutorial dataset\" \\\n--location \"My machine\" \\\n--data_path \"medperf_tutorial/sample_raw_data/images\" \\\n--labels_path \"medperf_tutorial/sample_raw_data/labels\" \\\n--benchmark 1\n

Once you run this command, the information to be submitted will be displayed on the screen and you will be asked to confirm your submission. Once you confirm, your dataset will be successfully registered!

"},{"location":"getting_started/data_owner_demo/#2-prepare-your-data","title":"2. Prepare your Data","text":"

To prepare and preprocess your dataset, you need to know the server UID of your registered dataset. You can check your datasets information by running:

medperf dataset ls --mine\n

In our tutorial, your dataset ID will be 1. Run the following command to prepare your dataset:

medperf dataset prepare --data_uid 1\n

This command will also calculate statistics on your data; statistics defined by the benchmark owner. These will be submitted to the MedPerf server in the next step upon your approval.

"},{"location":"getting_started/data_owner_demo/#3-mark-your-dataset-as-operational","title":"3. Mark your Dataset as Operational","text":"

After successfully preparing your dataset, you can mark it as ready so that it can be associated with benchmarks you want. During preparation, your dataset is considered in the Development stage, and now you will mark it as operational.

Note

Once marked as operational, it can never be marked as in-development anymore.

Run the following command to mark your dataset as operational:

medperf dataset set_operational --data_uid 1\n

Once you run this command, you will see on your screen the updated information of your dataset along with the statistics mentioned in the previous step. You will be asked to confirm submission of the displayed information. Once you confirm, your dataset will be successfully marked as operational!

Next, you can proceed to request participation in the benchmark by initiating an association request.

"},{"location":"getting_started/data_owner_demo/#4-request-participation","title":"4. Request Participation","text":"

For submitting the results of executing the benchmark models on your data in the future, you must associate your data with the benchmark.

Once you have submitted your dataset to the MedPerf server, it will be assigned a server UID, which you can find by running medperf dataset ls --mine. Your dataset's server UID is also 1.

Run the following command to request associating your dataset with the benchmark:

medperf dataset associate --benchmark_uid 1 --data_uid 1\n

This command will first run the benchmark's reference model on your dataset to ensure your dataset is compatible with the benchmark workflow. Then, the association request information is printed on the screen, which includes an executive summary of the test mentioned. You will be prompted to confirm sending this information and initiating this association request.

"},{"location":"getting_started/data_owner_demo/#how-to-proceed-after-requesting-association","title":"How to proceed after requesting association","text":"

When participating with a real benchmark, you must wait for the Benchmark Committee to approve the association request. You can check the status of your association requests by running medperf association ls. The association is identified by the server UIDs of your dataset and the benchmark with which you are requesting association.

For the sake of continuing the tutorial only, run the following to simulate the benchmark committee approving your association (make sure you are in the MedPerf's root directory):

sh tutorials_scripts/simulate_data_association_approval.sh\n

You can verify if your association request has been approved by running medperf association ls.

"},{"location":"getting_started/data_owner_demo/#5-execute-the-benchmark","title":"5. Execute the Benchmark","text":"

MedPerf provides a command that runs all the models of a benchmark effortlessly. You only need to provide two parameters:

  • The benchmark ID you want to run, which is 1.
  • The server UID of your data, which is 1.

For that, run the following command:

medperf benchmark run --benchmark 1 --data_uid 1\n

After running the command, you will receive a summary of the executions. You will see something similar to the following:

  model  local result UID    partial result    from cache    error\n-------  ------------------  ----------------  ------------  -------\n      2  b1m2d1              False             True\n      4  b1m4d1              False             False\nTotal number of models: 2\n        1 were skipped (already executed), of which 0 have partial results\n        0 failed\n        1 ran successfully, of which 0 have partial results\n\n\u2705 Done!\n

This means that the benchmark has two models:

  • A model that you already ran when you requested the association. This explains why it was skipped.
  • Another model that ran successfully. Its result generated UID is b1m4d1.

You can view the results by running the following command with the specific local result UID. For example:

medperf result view b1m4d1\n

For now, your results are only local. Next, you will learn how to submit the results.

"},{"location":"getting_started/data_owner_demo/#6-submit-a-result","title":"6. Submit a Result","text":"

After executing the benchmark, you will submit a result to the MedPerf server. To do so, you have to find the target result generated UID.

As an example, you will be submitting the result of UID b1m4d1. To do this, run the following command:

medperf result submit --result b1m4d1\n

The information that is going to be submitted will be printed to the screen and you will be prompted to confirm that you want to submit.

"},{"location":"getting_started/data_owner_demo/#cleanup-optional","title":"Cleanup (Optional)","text":"

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

  • To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

  • To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

rm -fr medperf_tutorial\n
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
cd server\nsh reset_db.sh\n
  • To cleanup the test storage:
rm -fr ~/.medperf/localhost_8000\n
"},{"location":"getting_started/installation/","title":"Installation","text":""},{"location":"getting_started/installation/#prerequisites","title":"Prerequisites","text":""},{"location":"getting_started/installation/#python","title":"Python","text":"

Make sure you have Python 3.9 installed along with pip. To check if they are installed, run:

python --version\npip --version\n

or, depending on you machine configuration:

python3 --version\npip3 --version\n

We will assume the commands' names are pip and python. Use pip3 and python3 if your machine is configured differently.

"},{"location":"getting_started/installation/#docker-or-singularity","title":"Docker or Singularity","text":"

Make sure you have the latest version of Docker or Singularity 3.10 installed.

To verify docker is installed, run:

docker --version\n

To verify singularity is installed, run:

singularity --version\n

If using Docker, make sure you can run Docker as a non-root user.

"},{"location":"getting_started/installation/#install-medperf","title":"Install MedPerf","text":"
  1. (Optional) MedPerf is better to be installed in a virtual environment. We recommend using Anaconda. Having anaconda installed, create a virtual environment medperf-env with the following command:

    conda create -n medperf-env python=3.9\n

    Then, activate your environment:

    conda activate medperf-env\n
  2. Clone the MedPerf repository:

    git clone https://github.com/mlcommons/medperf.git\ncd medperf\n
  3. Install MedPerf from source:

    pip install -e ./cli\n
  4. Verify the installation:

    medperf --version\n
"},{"location":"getting_started/installation/#whats-next","title":"What's Next?","text":"
  • Get familiar with the MedPerf client by following the hands-on tutorials.
  • Understand and learn how to build MedPerf MLCubes.
"},{"location":"getting_started/model_owner_demo/","title":"Model Owners","text":""},{"location":"getting_started/model_owner_demo/#hands-on-tutorial-for-model-owners","title":"Hands-on Tutorial for Model Owners","text":""},{"location":"getting_started/model_owner_demo/#overview","title":"Overview","text":"

In this guide, you will learn how a Model Owner can use MedPerf to take part in a benchmark. It's highly recommend that you follow this or this guide first to implement your own model MLCube and use it throughout this tutorial. However, this guide provides an already implemented MLCube if you want to directly proceed to learn how to interact with MedPerf.

The main tasks of this guide are:

  1. Testing MLCube compatibility with the benchmark.
  2. Submitting the MLCube.
  3. Requesting participation in a benchmark.

It's assumed that you have already set up the general testing environment as explained in the setup guide.

"},{"location":"getting_started/model_owner_demo/#before-you-start","title":"Before You Start","text":""},{"location":"getting_started/model_owner_demo/#first-steps","title":"First steps","text":""},{"location":"getting_started/model_owner_demo/#running-in-cloud-via-github-codespaces","title":"Running in cloud via Github Codespaces","text":"

As the most easy way to play with the tutorials you can launch a preinstalled Codespace cloud environment for MedPerf by clicking this link:

"},{"location":"getting_started/model_owner_demo/#running-in-local-environment","title":"Running in local environment","text":"

To start experimenting with MedPerf through this tutorial on your local machine, you need to start by following these quick steps:

  1. Install Medperf
  2. Set up Medperf
"},{"location":"getting_started/model_owner_demo/#prepare-the-local-medperf-server","title":"Prepare the Local MedPerf Server","text":"

For the purpose of the tutorial, you have to initialize a local MedPerf server with a fresh database and then create the necessary entities that you will be interacting with. To do so, run the following: (make sure you are in MedPerf's root folder)

cd server\nsh reset_db.sh\npython seed.py --demo model\ncd ..\n
"},{"location":"getting_started/model_owner_demo/#download-the-necessary-files","title":"Download the Necessary files","text":"

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

sh tutorials_scripts/setup_model_tutorial.sh\n

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. The folder contains the following content:

Toy content description

In real life all the listed artifacts and files have to be created on your own. However, for tutorial's sake you may use this toy data.

"},{"location":"getting_started/model_owner_demo/#model-mlcube","title":"Model MLCube","text":"

The medperf_tutorial/model_mobilenetv2/ is a toy Model MLCube. Once you submit your model to the benchmark, all participating Data Owners would be able to run the model within the benchmark pipeline. Therefore, your MLCube must support the specific input/output formats defined by the Benchmark Owners.

For the purposes of this tutorial, you will work with a pre-prepared toy benchmark. In a real-world scenario, you should refer to your Benchmark Owner to get a format specifications and details for your practical case.

"},{"location":"getting_started/model_owner_demo/#login-to-the-local-medperf-server","title":"Login to the Local MedPerf Server","text":"

The local MedPerf server is pre-configured with a dummy local authentication system. Remember that when you are communicating with the real MedPerf server, you should follow the steps in this guide to login. For the tutorials, you should not do anything.

You are now ready to start!

"},{"location":"getting_started/model_owner_demo/#1-test-your-mlcube-compatibility","title":"1. Test your MLCube Compatibility","text":"

Before submitting your MLCube, it is highly recommended that you test your MLCube compatibility with the benchmarks of interest to avoid later edits and multiple submissions. Your MLCube should be compatible with the benchmark workflow in two main ways:

  1. It should expect a specific data input structure
  2. Its outputs should follow a particular structure expected by the benchmark's metrics evaluator MLCube

These details should usually be acquired by contacting the Benchmark Committee and following their instructions.

To test your MLCube validity with the benchmark, first run medperf benchmark ls to identify the benchmark's server UID. In this case, it is going to be 1.

Next, locate the MLCube. Unless you implemented your own MLCube, the MLCube provided for this tutorial is located in your workspace: medperf_tutorial/model_mobilenetv2/mlcube/mlcube.yaml.

After that, run the compatibility test:

medperf test run \\\n--benchmark 1 \\\n--model \"medperf_tutorial/model_mobilenetv2/mlcube/mlcube.yaml\"\n

Assuming the test passes successfuly, you are ready to submit the MLCube to the MedPerf server.

"},{"location":"getting_started/model_owner_demo/#2-submit-the-mlcube","title":"2. Submit the MLCube","text":""},{"location":"getting_started/model_owner_demo/#how-does-medperf-recognize-an-mlcube","title":"How does MedPerf Recognize an MLCube?","text":"

The MedPerf server registers an MLCube as metadata comprised of a set of files that can be retrieved from the internet. This means that before submitting an MLCube you have to host its files on the internet. The MedPerf client provides a utility to prepare the files of an MLCube that need to be hosted. You can refer to this page if you want to understand what the files are, but using the utility script is enough.

To prepare the files of the MLCube, run the following command ensuring you are in MedPerf's root folder:

python scripts/package-mlcube.py --mlcube medperf_tutorial/model_mobilenetv2/mlcube --mlcube-types model\n

This script will create a new folder in the MLCube directory, named assets, containing all the files that should be hosted separately.

"},{"location":"getting_started/model_owner_demo/#host-the-files","title":"Host the Files","text":"

For the tutorial to run smoothly, the files are already hosted. If you wish to host them by yourself, you can find the list of supported options and details about hosting files in this page.

"},{"location":"getting_started/model_owner_demo/#submit-the-mlcube","title":"Submit the MLCube","text":"

The submission should include the URLs of all the hosted files. For the MLCube provided for the tutorial:

  • The URL to the hosted mlcube manifest file is
https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/mlcube.yaml\n
  • The URL to the hosted mlcube parameters file is
https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/workspace/parameters.yaml\n
  • The URL to the hosted additional files tarball file is
https://storage.googleapis.com/medperf-storage/chestxray_tutorial/mobilenetv2_weights.tar.gz\n

Use the following command to submit:

medperf mlcube submit \\\n--name my-model-cube \\\n--mlcube-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/mlcube.yaml\" \\\n--parameters-file \"https://raw.githubusercontent.com/mlcommons/medperf/main/examples/chestxray_tutorial/model_mobilenetv2/mlcube/workspace/parameters.yaml\" \\\n--additional-file \"https://storage.googleapis.com/medperf-storage/chestxray_tutorial/mobilenetv2_weights.tar.gz\" \\\n--operational\n

The MLCube will be assigned by a server UID. You can check it by running:

medperf mlcube ls --mine\n
"},{"location":"getting_started/model_owner_demo/#3-request-participation","title":"3. Request Participation","text":"

Benchmark workflows are run by Data Owners, who will get notified when a new model is added to a benchmark. You must request the association for your model to be part of the benchmark.

To initiate an association request, you need to collect the following information:

  • The target benchmark ID, which is 1
  • The server UID of your MLCube, which is 4.

Run the following command to request associating your MLCube with the benchmark:

medperf mlcube associate --benchmark 1 --model_uid 4\n

This command will first run the benchmark's workflow on your model to ensure your model is compatible with the benchmark workflow. Then, the association request information is printed on the screen, which includes an executive summary of the test mentioned. You will be prompted to confirm sending this information and initiating this association request.

"},{"location":"getting_started/model_owner_demo/#what-happens-after-requesting-the-association","title":"What Happens After Requesting the Association?","text":"

When participating with a real benchmark, you must wait for the Benchmark Committee to approve the association request. You can check the status of your association requests by running medperf association ls. The association is identified by the server UIDs of your MLCube and the benchmark with which you are requesting association.

"},{"location":"getting_started/model_owner_demo/#cleanup-optional","title":"Cleanup (Optional)","text":"

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

  • To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

  • To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

rm -fr medperf_tutorial\n
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
cd server\nsh reset_db.sh\n
  • To cleanup the test storage:
rm -fr ~/.medperf/localhost_8000\n
"},{"location":"getting_started/overview/","title":"Overview","text":"

The MedPerf client provides all the necessary tools to run a complete benchmark experiment. Below, you will find a comprehensive breakdown of user roles and the corresponding functionalities they can access and perform using the MedPerf client:

  • Benchmark Committee: The Benchmark Commitee can define and create a benchmark, as well as manage experiments (e.g., approving which datasets and models will be allowed to participate)
  • Model Owner: The Model Owner can submit a model to the MedPerf server and request participation in a benchmark.
  • Data Owner: The Data Owner can prepare their raw medical data, register the metadata of their prepared dataset, request participation in a benchmark, execute a benchmark's models on their data, and submit the results of the execution.
"},{"location":"getting_started/setup/","title":"Setup","text":"

This setup is only for running the tutorials. If you are using MedPerf with a real benchmark and real experiments, skip to this section to optionally change your container runner. Then, follow the tutorials as a general guidance for your real experiments.

"},{"location":"getting_started/setup/#install-the-medperf-client","title":"Install the MedPerf Client","text":"

If this is your first time using MedPerf, install the MedPerf client library as described here.

"},{"location":"getting_started/setup/#run-a-local-medperf-server","title":"Run a Local MedPerf Server","text":"

For this tutorial, you should spawn a local MedPerf server for the MedPerf client to communicate with. Note that this server will be hosted on your localhost and not on the internet.

  1. Install the server requirements ensuring you are in MedPerf's root folder:

    pip install -r server/requirements.txt\npip install -r server/test-requirements.txt\n
  2. Run the local MedPerf server using the following command:

    cd server\ncp .env.local.local-auth .env\nsh setup-dev-server.sh\n

The local MedPerf server now is ready to recieve requests. You can always stop the server by pressing CTRL+C in the terminal where you ran the server.

After that, you will be configuring the MedPerf client to communicate with the local MedPerf server. Make sure you continue following the instructions in a new terminal.

"},{"location":"getting_started/setup/#configure-the-medperf-client","title":"Configure the MedPerf Client","text":"

The MedPerf client can be configured by creating or modifying \"profiles\". A profile is a set of configuration parameters used by the client during runtime. By default, the profile named default will be active.

The default profile is preconfigured so that the client communicates with the main MedPerf server (api.medperf.org). For the purposes of the tutorial, you will be using the local profile as it is preconfigured so that the client communicates with the local MedPerf server.

To activate the local profile, run the following command:

medperf profile activate local\n

You can always check which profile is active by running:

medperf profile ls\n

To view the current active profile's configured parameters, you can run the following:

medperf profile view\n
"},{"location":"getting_started/setup/#choose-the-container-runner","title":"Choose the Container Runner","text":"

You can configure the MedPerf client to use either Docker or Singularity. The local profile is configured to use Docker. If you want to use MedPerf with Singularity, modify the local profile configured parameters by running the following:

medperf profile set --platform singularity\n

This command will modify the platform parameter of the currently activated profile.

"},{"location":"getting_started/setup/#whats-next","title":"What's Next?","text":"

The local MedPerf server now is ready to recieve requests, and the MedPerf client is ready to communicate. Depending on your role, you can follow these hands-on tutorials:

  • How a benchmark committee can create and submit a benchmark.

  • How a model owner can submit a model.

  • How a data owner can prepare their data and execute a benchmark.

"},{"location":"getting_started/signup/","title":"Create your MedPerf Account","text":"

MedPerf uses passwordless authentication. This means that there will be no need for a password, and you have to access your email in order complete the signup process.

Automatic signups are currently disabled. Please contact the MedPerf team in order to provision an account.

Tip

You don't need an account to run the tutorials and learn how to use the MedPerf client.

"},{"location":"getting_started/signup/#whats-next","title":"What's Next?","text":"
  • Install the MedPerf client
"},{"location":"getting_started/tutorials_overview/","title":"Overview","text":"

The tutorials simulate a benchmarking example for the task of detecting thoracic diseases from chest X-ray scans. You can find the description of the used data here. Throughout the tutorials, you will be interacting with a temporary local MedPerf server as described in the setup page. This allows you to freely experiment with the MedPerf client and rerun the tutorials as many times as you want, providing you with an immersive learning experience. Please note that these tutorials also serve as a general guidance to be followed when using the MedPerf client in a real scenario.

Before proceeding to the tutorials, make sure you have the general tutorial environment set up.

To ensure users have the best experience in learning the fundamentals of MedPerf and utilizing the MedPerf client, the following set of tutorials are provided:

Benchmark Committee

Click here to see the documentation specifically for benchmark owners.

Model Owner

Click here to see the documentation specifically for model owners.

Data Owner

Click here to see the documentation specifically for data owners.

"},{"location":"getting_started/shared/before_we_start/","title":"Macro Rendering Error","text":"

File: getting_started/shared/before_we_start.md

UndefinedError: 'dict object' has no attribute 'tutorial_id'

Traceback (most recent call last):\n  File \"/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/mkdocs_macros/plugin.py\", line 527, in render\n    return md_template.render(**page_variables)\n  File \"/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/jinja2/environment.py\", line 1301, in render\n    self.environment.handle_exception()\n  File \"/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/jinja2/environment.py\", line 936, in handle_exception\n    raise rewrite_traceback_stack(source=source)\n  File \"<template>\", line 41, in top-level template code\njinja2.exceptions.UndefinedError: 'dict object' has no attribute 'tutorial_id'\n
"},{"location":"getting_started/shared/cleanup/","title":"Cleanup","text":""},{"location":"getting_started/shared/cleanup/#cleanup-optional","title":"Cleanup (Optional)","text":"

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

  • To shut down the local MedPerf server: press CTRL+C in the terminal where the server is running.

  • To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):

rm -fr medperf_tutorial\n
  • To cleanup the local MedPerf server database: (make sure you are in the MedPerf's root directory)
cd server\nsh reset_db.sh\n
  • To cleanup the test storage:
rm -fr ~/.medperf/localhost_8000\n
"},{"location":"getting_started/shared/mlcube_submission_overview/","title":"Mlcube submission overview","text":"

The MedPerf server registers an MLCube as metadata comprised of a set of files that can be retrieved from the internet. This means that before submitting an MLCube you have to host its files on the internet. The MedPerf client provides a utility to prepare the files of an MLCube that need to be hosted. You can refer to this page if you want to understand what the files are, but using the utility script is enough.

"},{"location":"getting_started/shared/redirect_to_hosting_files/","title":"Redirect to hosting files","text":""},{"location":"getting_started/shared/redirect_to_hosting_files/#host-the-files","title":"Host the Files","text":"

For the tutorial to run smoothly, the files are already hosted. If you wish to host them by yourself, you can find the list of supported options and details about hosting files in this page.

"},{"location":"getting_started/shared/tutorials_content_overview/benchmark/","title":"Benchmark","text":"

In this tutorial we will create a benchmark that classifies chest X-Ray images.

"},{"location":"getting_started/shared/tutorials_content_overview/benchmark/#demo-data","title":"Demo Data","text":"

The medperf_tutorial/demo_data/ folder contains the demo dataset content.

  • images/ folder includes sample images.
  • labels/labels.csv provides a basic ground truth markup, indicating the class each image belongs to.

The demo dataset is a sample dataset used for the development of your benchmark and used by Model Owners for the development of their models. More details are available in the section below

"},{"location":"getting_started/shared/tutorials_content_overview/benchmark/#data-preparator-mlcube","title":"Data Preparator MLCube","text":"

The medperf_tutorial/data_preparator/ contains a DataPreparator MLCube that you must implement. This MLCube: - Transforms raw data into a format convenient for model consumption, such as converting DICOM images into numpy tensors, cropping patches, normalizing columns, etc. It's up to you to define the format that is handy for future models. - Ensures its output is in a standardized format, allowing Model Owners/Developers to rely on its consistency.

"},{"location":"getting_started/shared/tutorials_content_overview/benchmark/#model-mlcube","title":"Model MLCube","text":"

The medperf_tutorial/model_custom_cnn/ is an example of a Model MLCube. You need to implement a reference model which will be used by data owners to test the compatibility of their data with your pipeline. Also, Model Developers joining your benchmark will follow the input/output specifications of this model when building their own models.

"},{"location":"getting_started/shared/tutorials_content_overview/benchmark/#metrics-mlcube","title":"Metrics MLCube","text":"

The medperf_tutorial/metrics/ houses a Metrics MLCube that processes ground truth data, model predictions, and computes performance metrics - such as classification accuracy, loss, etc. After a Dataset Owner runs the benchmark pipeline on their data, these final metric values will be shared with you as the Benchmark Owner.

"},{"location":"getting_started/shared/tutorials_content_overview/data/","title":"Data","text":""},{"location":"getting_started/shared/tutorials_content_overview/data/#tutorials-dataset-example","title":"Tutorial's Dataset Example","text":"

The medperf_tutorial/sample_raw_data/ folder contains your data for the specified Benchmark. In this tutorial, where the benchmark involves classifying chest X-Ray images, your data comprises:

  • images/ folder contains your images
  • labels/labels.csv, which provides the ground truth markup, specifying the class of each image.

The format of this data is dictated by the Benchmark Owner, as it must be compatible with the benchmark's Data Preparation MLCube. In a real-world scenario, the expected data format would differ from this toy example. Refer to the Benchmark Owner to get a format specifications and details for your practical case.

As previously mentioned, your data itself never leaves your machine. During the dataset submission, only basic metadata is transferred, for which you will be prompted to confirm.

"},{"location":"getting_started/shared/tutorials_content_overview/model/","title":"Model","text":""},{"location":"getting_started/shared/tutorials_content_overview/model/#model-mlcube","title":"Model MLCube","text":"

The medperf_tutorial/model_mobilenetv2/ is a toy Model MLCube. Once you submit your model to the benchmark, all participating Data Owners would be able to run the model within the benchmark pipeline. Therefore, your MLCube must support the specific input/output formats defined by the Benchmark Owners.

For the purposes of this tutorial, you will work with a pre-prepared toy benchmark. In a real-world scenario, you should refer to your Benchmark Owner to get a format specifications and details for your practical case.

"},{"location":"mlcubes/gandlf_mlcube/","title":"Creating a GaNDLF MLCube","text":""},{"location":"mlcubes/gandlf_mlcube/#overview","title":"Overview","text":"

This guide will walk you through how to wrap a model trained using GaNDLF as a MedPerf-compatible MLCube ready to be used for inference (i.e. as a Model MLCube). The steps can be summarized as follows:

  1. Train a GaNDLF model
  2. Create the MLCube file
  3. (Optional) Create a custom entrypoint
  4. Deploy the GaNDLF model as an MLCube

Before proceeding, make sure you have medperf installed and GaNDLF installed.

"},{"location":"mlcubes/gandlf_mlcube/#before-we-start","title":"Before We Start","text":""},{"location":"mlcubes/gandlf_mlcube/#download-the-necessary-files","title":"Download the Necessary files","text":"

A script is provided to download all the necessary files so that you follow the tutorial smoothly. Run the following: (make sure you are in MedPerf's root folder)

sh tutorials_scripts/setup_GaNDLF_mlcube_tutorial.sh\n

This will create a workspace folder medperf_tutorial where all necessary files are downloaded. Run cd medperf_tutorial to switch to this folder.

"},{"location":"mlcubes/gandlf_mlcube/#1-train-a-gandlf-model","title":"1. Train a GaNDLF Model","text":"

Train a small GaNDLF model to use for this guide. You can skip this step if you already have a trained model.

Make sure you are in the workspace folder medperf_tutorial. Run:

gandlf_run \\\n-c ./config_getting_started_segmentation_rad3d.yaml \\\n-i ./data.csv \\\n-m ./trained_model_output \\\n-t True \\\n-d cpu\n

Note that if you want to train on GPU you can use -d cuda, but the example used here should take only few seconds using the CPU.

Warning

This tutorial assumes the user is using the latest GaNDLF version. The configuration file config_getting_started_segmentation_rad3d.yaml will cause problems if you are using a different version, make sure you do the necessary changes.

You will now have your trained model and its related files in the folder trained_model_output. Next, you will start learning how to wrap this trained model within an MLCube.

"},{"location":"mlcubes/gandlf_mlcube/#2-create-the-mlcube-file","title":"2. Create the MLCube File","text":"

MedPerf provides a cookiecutter to create an MLCube file that is ready to be consumed by gandlf_deploy and produces an MLCube ready to be used by MedPerf. To create the MLCube, run: (make sure you are in the workspace folder medperf_tutorial)

medperf mlcube create gandlf\n

Note

MedPerf is running CookieCutter under the hood. This medperf command provides additional arguments for handling different scenarios. You can see more information on this by running medperf mlcube create --help

You will be prompted to customize the MLCube creation. Below is an example of how your response might look like:

project_name [GaNDLF MLCube]: My GaNDLF MLCube # (1)!\nproject_slug [my_gandlf_mlcube]: my_gandlf_mlcube # (2)!\ndescription [GaNDLF MLCube Template. Provided by MLCommons]: GaNDLF MLCube implementation # (3)!\nauthor_name [John Smith]: John Smith # (4)!\naccelerator_count [1]: 0 # (5)!\ndocker_build_file [Dockerfile-CUDA11.6]: Dockerfile-CPU # (6)!\ndocker_image_name [docker/image:latest]: johnsmith/gandlf_model:0.0.1 # (7)!\n
  1. Gives a Human-readable name to the MLCube Project.
  2. Determines how the MLCube root folder will be named.
  3. Gives a Human-readable description to the MLCube Project.
  4. Documents the MLCube implementation by specifying the author. Please use your own name here.
  5. Indicates how many GPUs should be visible by the MLCube.
  6. Indicates the Dockerfile name from GaNDLF that should be used for building your docker image. Use the name of the Dockerfile that aligns with your model's dependencies. Any \"Dockerfile-*\" in the GaNDLF source repository is valid.
  7. MLCubes use containers under the hood. Medperf supports both Docker and Singularity. Here, you can provide an image tag to the image that will be created by this MLCube. It's recommended to use a naming convention that allows you to upload it to Docker Hub.

Assuming you chose my_gandlf_mlcube as the project slug, you will find your MLCube created under the folder my_gandlf_mlcube. Next, you will use a GaNDLF utility to build the MLCube.

Note

You might need to specify additional configurations in the mlcube.yaml file if you are using a GPU. Check the generated mlcube.yaml file for more info, as well as the MLCube documentation.

"},{"location":"mlcubes/gandlf_mlcube/#3-optional-create-a-custom-entrypoint","title":"3. (Optional) Create a Custom Entrypoint","text":"

When deploying the GaNDLF model directly as a model MLCube, the default entrypoint will be gandlf_run .... You can override the entrypoint with a custom python script. One of the usecases is described below.

gandlf_run expects a data.csv file in the input data folder, which describes the inference test cases and their associated paths (Read more about GaNDLF's csv file conventions here). In case your MLCube will expect a data folder with a predefined data input structure but without this csv file, you can use a custom script that prepares this csv file as an entrypoint. You can find the recommended template and an example here.

"},{"location":"mlcubes/gandlf_mlcube/#4-deploy-the-gandlf-model-as-an-mlcube","title":"4. Deploy the GaNDLF Model as an MLCube","text":"

To deploy the GaNDLF model as an MLCube, run the following: (make sure you are in the workspace folder medperf_tutorial)

gandlf_deploy \\\n-c ./config_getting_started_segmentation_rad3d.yaml \\\n-m ./trained_model_output \\\n--target docker \\\n--mlcube-root ./my_gandlf_mlcube \\\n-o ./built_gandlf_mlcube \\\n--mlcube-type model \\\n--entrypoint <(optional) path to your custom entrypoint script> \\ # (1)!\n-g False # (2)!\n
  1. If you are not using a custom entrypoint, ignore this option.
  2. Change to True if you want the resulting MLCube to use a GPU for inference.

GaNDLF will use your initial MLCube configuration my_gandlf_mlcube, the GaNDLF experiment configuration file config_classification.yaml, and the trained model trained_model_output to create a ready MLCube built_gandlf_mlcube and build the docker image that will be used by the MLCube. The docker image will have the model weights and the GaNDLF experiment configuration file embedded. You can check that your image was built by running docker image ls. You will see johnsmith/gandlf_model:0.0.1 (or whatever image name that was used) created moments ago.

"},{"location":"mlcubes/gandlf_mlcube/#5-next-steps","title":"5. Next Steps","text":"

That's it! You have built a MedPerf-compatible MLCube with GaNDLF. You may want to submit your MLCube to MedPerf, you can follow this tutorial.

Tip

MLCubes created by GaNDLF have the model weights and configuration file embedded in the docker image. When you want to deploy your MLCube for MedPerf, all you need to do is pushing the docker image and hosting the mlcube.yaml file.

"},{"location":"mlcubes/gandlf_mlcube/#cleanup-optional","title":"Cleanup (Optional)","text":"

You have reached the end of the tutorial! If you are planning to rerun any of the tutorials, don't forget to cleanup:

  • To cleanup the downloaded files workspace (make sure you are in the MedPerf's root directory):
rm -fr medperf_tutorial\n
"},{"location":"mlcubes/gandlf_mlcube/#see-also","title":"See Also","text":"
  • Creating a Model MLCube from scratch.
"},{"location":"mlcubes/mlcube_data/","title":"In Progress","text":""},{"location":"mlcubes/mlcube_data_WIP/","title":"mlcube data WIP","text":"

TODO: Change the structure to align with mlcube_models, to help users wrap their existing code into mlcube

"},{"location":"mlcubes/mlcube_data_WIP/#data-preparator-mlcube","title":"Data Preparator MLCube","text":""},{"location":"mlcubes/mlcube_data_WIP/#introduction","title":"Introduction","text":"

This guide is one of three designed to assist users in building MedPerf-compatible MLCubes. The other two guides focus on creating a Model MLCube and a Metrics MLCube. Together, these three MLCubes form a complete benchmark workflow for the task of thoracic disease detection from Chest X-rays.

In summary, a functional MedPerf pipeline includes these steps:

  1. The Data Owner exports a raw dataset from their databases (manually or via scripts) - this step occurs outside the pipeline itself. Let's name the output folder as my_raw_data/. If the pipeline is run by another person (Model Owner/Benchmark Owner), a predefined my_benchmark_demo_raw_data/ would be used instead (created and distributed by the Benchmark Owner).
  2. The Data Preparator MLCube takes this folder's path as input and converts data into a standardized format, resulting in some my_prepared_dataset/ folder (MLCube is implemented by the Benchmark Owner).
  3. The Model MLCube processes the prepared data, running a model and saving the results in some my_model_predictions/ folder (MLCube is implemented by the Model Owner; the Benchmark Owner must implement a baseline model MLCube to be used as a mock-up).
  4. The Metrics MLCube processes predictions and evaluates metrics, saving them in some my_metrics.yaml file (MLCube implemented by the Benchmark Owner).
  5. The Data Owner reviews the metric results and may submit them to the MedPerf server.

Aforementioned guides detail steps 2-4. As all steps demonstrate building specific MLCubes, we recommend starting with the Model MLCube guide, which offers a more detailed explanation of the MLCube's concept and structure. Another option is to explore MLCube basic docs. In this guide provides the shortened concepts description, focusing on nuances and input/output parameters.

"},{"location":"mlcubes/mlcube_data_WIP/#about-this-guide","title":"About this Guide","text":"

This guide describes the tasks, structure and input/output parameters of Data Preparator MLCube, allowing users at the end to be able to implement their own MedPerf-compatible MLCube for Benchmark purposes.

The guide starts with general advices, steps, and the required API for building these MLCubes. Subsequently, it will lead you through creating your MLCube using the Chest X-ray Data Preprocessor MLCube as a practical example.

It's considered best practice to handle data in various formats. For instance, if the benchmark involves image processing, it's beneficial to support JPEGs, PNGs, BMPs, and other expected image formats; accommodate large and small images, etc. Such flexibility simplifies the process for Dataset Owners, allowing them to export data in their preferred format. The Data Preparator's role is to convert all reasonable input data into a unified format.

"},{"location":"mlcubes/mlcube_data_WIP/#before-building-the-mlcube","title":"Before Building the MLCube","text":"

Your MLCube must implement three command tasks:

  • prepare: your main task that transforms raw input data into a unified format.
  • sanity_check: verifies the cleanliness and consistency of prepare outputs (e.g., ensuring no records lack ground truth labels, labels contain only expected values, data fields are reasonable without outliers or NaNs, etc.)
  • statistics: Calculates some aggregated statistics on the transformed dataset. Once the Dataset Owner submits their dataset, these statistics will be uploaded to you as the Benchmark Owner.

It's assumed that you already have:

  • Some raw data.
  • The expected unified format for data conversion.
  • A working implementation of all three tasks.

This guide will help you encapsulate your preparation code within an MLCube. Make sure you extracted each part of your logic, so it can be run independently.

"},{"location":"mlcubes/mlcube_data_WIP/#required-api","title":"Required API","text":"

Each command execution receives specific parameters. While you are flexible in code implementation, keep in mind that your implementation will receive the following input arguments:

"},{"location":"mlcubes/mlcube_data_WIP/#data-preparation-api-prepare-command","title":"Data Preparation API (prepare Command)","text":"

The parameters include: - data_path: the path to the raw data folder (read-only). - labels_path: the path to the ground truth labels folder (read-only). - Any other optional extra params that you attach to the MLCube, such as path to .txt file with acceptable labels. Note: these extra parameters contain values defined by you, the MLCube owner, not the users' data. - output_path: an r/w folder for storing transformed dataset objects. - output_labels_path: an r/w folder for storing transformed labels.

"},{"location":"mlcubes/mlcube_data_WIP/#sanity-check-api-sanity_check-command","title":"Sanity Check API (sanity_check Command)","text":"

The parameters include: - data_path: the path to the transformed data folder (read-only). - labels_path: the path to the transformed ground truth labels folder (read-only). - Any other optional extra params that you attach to the MLCube - same as for the prepare command.

The sanity check does not produce outputs; it either completes successfully or fails.

"},{"location":"mlcubes/mlcube_data_WIP/#statistics-api-statistics-command","title":"Statistics API (statistics Command)","text":"
  • data_path: the path to the transformed data folder (read-only).
  • labels_path: the path to the transformed ground truth labels folder (read-only).
  • Any other optional extra params that you attach to the MLCube - same as for prepare command.
  • output_path: path to .yaml file where your code should write down calculated statistics.
"},{"location":"mlcubes/mlcube_data_WIP/#build-your-own-mlcube","title":"Build Your Own MLCube","text":"

While this guide leads you through creating your own MLCube, you can always check a prebuilt example for a better understanding of how it works in an already implemented MLCube. The example is available here:

cd examples/chestxray_tutorial/data_preparator/\n

The guide uses this implementation to describe concepts.

"},{"location":"mlcubes/mlcube_data_WIP/#use-an-mlcube-template","title":"Use an MLCube Template","text":"

First, ensure you have MedPerf installed. Create a Data Preparator MLCube template by running the following command:

medperf mlcube create data_preparator\n

You will be prompted to fill in some configuration options through the CLI. Below are the options and their default values:

project_name [Data Preparator MLCube]: # (1)!\nproject_slug [data_preparator_mlcube]: # (2)!\ndescription [Data Preparator MLCube Template. Provided by MLCommons]: # (3)!\nauthor_name [John Smith]: # (4)!\naccelerator_count [0]: # (5)!\ndocker_image_name [docker/image:latest]: # (6)!\n
  1. Gives a Human-readable name to the MLCube Project.
  2. Determines how the MLCube root folder will be named.
  3. Gives a Human-readable description to the MLCube Project.
  4. Documents the MLCube implementation by specifying the author.
  5. Specifies how many GPUs should be visible by the MLCube.
  6. MLCubes use Docker containers under the hood. Here, you can provide an image tag for the image created by this MLCube. You should use a valid name that allows you to upload it to a Docker registry.

After filling the configuration options, the following directory structure will be generated:

.\n\u2514\u2500\u2500 evaluator_mlcube\n    \u251c\u2500\u2500 mlcube\n    \u2502   \u251c\u2500\u2500 mlcube.yaml\n    \u2502   \u2514\u2500\u2500 workspace\n    \u2502       \u2514\u2500\u2500 parameters.yaml\n    \u2514\u2500\u2500 project\n        \u251c\u2500\u2500 Dockerfile\n        \u251c\u2500\u2500 mlcube.py\n        \u2514\u2500\u2500 requirements.txt\n
"},{"location":"mlcubes/mlcube_data_WIP/#the-project-folder","title":"The project Folder","text":"

This is where your preprocessing logic will live. It contains a standard Docker image project with a specific API for the entrypoint. mlcube.py contains the entrypoint and handles all the tasks we've described. Update this template with your code and bind your logic to specified functions for all three commands. Refer to the Chest X-ray tutorial example for an example of how it should look:

mlcube.py
\"\"\"MLCube handler file\"\"\"\nimport typer\nimport yaml\nfrom prepare import prepare_dataset\nfrom sanity_check import perform_sanity_checks\nfrom stats import generate_statistics\napp = typer.Typer()\n@app.command(\"prepare\")\ndef prepare(\ndata_path: str = typer.Option(..., \"--data_path\"),\nlabels_path: str = typer.Option(..., \"--labels_path\"),\nparameters_file: str = typer.Option(..., \"--parameters_file\"),\noutput_path: str = typer.Option(..., \"--output_path\"),\noutput_labels_path: str = typer.Option(..., \"--output_labels_path\"),\n):\nwith open(parameters_file) as f:\nparameters = yaml.safe_load(f)\nprepare_dataset(data_path, labels_path, parameters, output_path, output_labels_path)\n@app.command(\"sanity_check\")\ndef sanity_check(\ndata_path: str = typer.Option(..., \"--data_path\"),\nlabels_path: str = typer.Option(..., \"--labels_path\"),\nparameters_file: str = typer.Option(..., \"--parameters_file\"),\n):\nwith open(parameters_file) as f:\nparameters = yaml.safe_load(f)\nperform_sanity_checks(data_path, labels_path, parameters)\n@app.command(\"statistics\")\ndef statistics(\ndata_path: str = typer.Option(..., \"--data_path\"),\nlabels_path: str = typer.Option(..., \"--labels_path\"),\nparameters_file: str = typer.Option(..., \"--parameters_file\"),\nout_path: str = typer.Option(..., \"--output_path\"),\n):\nwith open(parameters_file) as f:\nparameters = yaml.safe_load(f)\ngenerate_statistics(data_path, labels_path, parameters, out_path)\nif __name__ == \"__main__\":\napp()\n
"},{"location":"mlcubes/mlcube_data_WIP/#the-mlcube-folder","title":"The mlcube Folder","text":"

This folder is primarily for configuring your MLCube and providing additional files the MLCube may interact with, such as parameters or model weights.

"},{"location":"mlcubes/mlcube_data_WIP/#mlcubeyaml-mlcube-configuration","title":"mlcube.yaml MLCube Configuration","text":"

The mlcube/mlcube.yaml file contains metadata and configuration of your mlcube. This file is already populated with the configuration you provided during the template creation step. There is no need to edit anything in this file except if you are specifying extra parameters to the commands (e.g., you want to pass a sklearn's StardardScaler weights or any other parameters required for data transformation).

mlcube.py
name: Chest X-ray Data Preparator\ndescription: MedPerf Tutorial - Data Preparation MLCube.\nauthors:\n- { name: MLCommons Medical Working Group }\nplatform:\naccelerator_count: 0\ndocker:\n# Image name\nimage: mlcommons/chestxray-tutorial-prep:0.0.0\n# Docker build context relative to $MLCUBE_ROOT. Default is `build`.\nbuild_context: \"../project\"\n# Docker file name within docker build context, default is `Dockerfile`.\nbuild_file: \"Dockerfile\"\ntasks:\nprepare:\nparameters:\ninputs:\n{\ndata_path: input_data,\nlabels_path: input_labels,\nparameters_file: parameters.yaml,\n}\noutputs: { output_path: data/, output_labels_path: labels/ }\nsanity_check:\nparameters:\ninputs:\n{\ndata_path: data/,\nlabels_path: labels/,\nparameters_file: parameters.yaml,\n}\nstatistics:\nparameters:\ninputs:\n{\ndata_path: data/,\nlabels_path: labels/,\nparameters_file: parameters.yaml,\n}\noutputs: { output_path: { type: file, default: statistics.yaml } }\n

All paths are relative to mlcube/workspace/ folder.

To set up additional inputs, add a key-value pair in the task's inputs dictionary:

mlcube.yaml
...\nprepare:\nparameters:\ninputs:\n{\n          data_path: input_data,\n          labels_path: input_labels,\n          parameters_file: parameters.yaml,\n          standardscaler_weights: additional_files/standardscaler.pkl\n}\noutputs: { output_path: data/, output_labels_path: labels/ }\n...\n

Considering the note about path locations, this new file should be stored at mlcube/workspace/additional_files/standardscaler.pkl

"},{"location":"mlcubes/mlcube_data_WIP/#parameters","title":"Parameters","text":"

Your preprocessing logic might depend on certain parameters (e.g., which labels are accepted). It is generally better to pass such parameters when running the MLCube, rather than hardcoding them. This can be done via a parameters.yaml file that is passed to the MLCube. This file will be available to the previously described commands (if you declare it in the inputs dict of a specific command). You can parse this file in the mlcube.py file and pass its contents to your logic.

This file should be placed in the mlcube/workspace folder.

"},{"location":"mlcubes/mlcube_data_WIP/#build-your-mlcube","title":"Build Your MLCube","text":"

After you follow the previous sections and fulfill the image with your logic, the MLCube is ready to be built and run. Run the command below to build the MLCube. Ensure you are in the mlcube/ subfolder of your Data Preparator.

mlcube configure -Pdocker.build_strategy=always\n

This command builds your Docker image and prepares the MLCube for use.

"},{"location":"mlcubes/mlcube_data_WIP/#run-your-mlcube","title":"Run Your MLCube","text":"

MedPerf will take care of running your MLCube. However, it's recommended to test the MLCube alone before using it with MedPerf for better debugging.

To run the MLCube, use the command below. Ensure you are located in the mlcube/ subfolder of your Data Preparator.

mlcube run --task prepare data_path=<path_to_raw_data> \\\nlabels_path=<path_to_raw_labels> \\\noutput_path=<path_to_save_transformed_data> \\\noutput_labels_path=<path_to_save_transformed_labels>\n

Relative paths

Keep in mind that though we are running tasks from mlcube/, all the paths should be absolute or relative to mlcube/workspace/.

Default values

We have declared a default values for every path parameter. This allows for omitting these parameters in our commands.

Consider the following structure:

.\n\u2514\u2500\u2500 data_preparator_mlcube\n    \u251c\u2500\u2500 mlcube\n    \u2502   \u251c\u2500\u2500 mlcube.yaml\n    \u2502   \u2514\u2500\u2500 workspace\n    \u2502       \u2514\u2500\u2500 parameters.yaml\n    \u2514\u2500\u2500 project\n        \u2514\u2500\u2500 ...\n\u2514\u2500\u2500 my_data\n    \u251c\u2500\u2500 data\n    \u2502   \u251c\u2500\u2500 ...\n    \u2514\u2500\u2500 labels\n        \u2514\u2500\u2500 ...\n

Now, you can execute the commands below, being located at data_preparator_mlcube/mlcube/:

mlcube run --task prepare data_path=../../my_data/data/ labels_path=../../my_data/labels/\nmlcube run --task sanity_check\nmlcube run --task statistics output_path=../../my_data/statistics.yaml\n

Note that:

  1. The passed paths are relative to mlcube/workspace rather then to the current working directory,
  2. We used default values for transformed data so new folders would appear: mlcube/workspace/data/ and others.
"},{"location":"mlcubes/mlcube_data_WIP/#using-the-example-with-gpus","title":"Using the Example with GPUs","text":"

The provided example codebase runs only on CPU. You can modify it to pass a GPU inside Docker image if your code utilizes it.

The general instructions for building an MLCube to work with a GPU are the same as the provided instructions, but with the following slight modifications:

  • Enter a number different than 0 for the accelerator_count that you will be prompted with when creating the MLCube template or modify platform.accelerator_count value of mlcube.yaml configuration.
  • Inside the docker section of the mlcube.yaml, add a key value pair: gpu_args: --gpus=all. These gpu_args will be passed to docker run command by MLCube. You may add more than just --gpus=all.
  • Make sure you install the required GPU dependencies in the docker image. For instance, this may be done by simply modifying the pip dependencies in the requirements.txt file to download pytorch with cuda, or by changing the base image of the dockerfile.
"},{"location":"mlcubes/mlcube_metrics/","title":"In Progress","text":""},{"location":"mlcubes/mlcube_metrics_WIP/","title":"mlcube metrics WIP","text":"

TODO: Change the structure to align with mlcube_models, to help users wrap their existing code into mlcube

"},{"location":"mlcubes/mlcube_metrics_WIP/#metricsevaluator-mlcube","title":"Metrics/Evaluator MLCube","text":""},{"location":"mlcubes/mlcube_metrics_WIP/#introduction","title":"Introduction","text":"

This guide is one of three designed to assist users in building MedPerf-compatible MLCubes. The other two guides focus on creating a Data Preparator MLCube and a Model MLCube. Together, these three MLCubes form a complete benchmark workflow for the task of thoracic disease detection from Chest X-rays.

In summary, a functional MedPerf pipeline includes these steps:

  1. The Data Owner exports a raw dataset from their databases (manually or via scripts) - this step occurs outside the pipeline itself. Lets name the output folder as my_raw_data/. If the pipeline is run by another person (Model Owner/Benchmark Owner), a predefined my_benchmark_demo_raw_data/ would be used instead (created and distributed by the Benchmark Owner).
  2. The Data Preparator MLCube takes this folder's path as input and converts data into a standardized format, resulting in some my_prepared_dataset/ folder (MLCube is implemented by the Benchmark Owner).
  3. The Model MLCube processes the prepared data, running a model and saving the results in some my_model_predictions/ folder (MLCube is implemented by the Model Owner; the Benchmark Owner must implement a baseline model MLCube to be used as a mock-up).
  4. The Metrics/Evaluator MLCube processes predictions and evaluates metrics, saving them in some my_metrics.yaml file (MLCube implemented by the Benchmark Owner).
  5. The Data Owner reviews the metric results and may submit them to the MedPerf server.

Aforementioned guides detail steps 2-4. As all steps demonstrate building specific MLCubes, we recommend starting with the Model MLCube guide, which offers a more detailed explanation of the MLCube's concept and structure. Another option is to explore MLCube basic docs. In this guide provides the shortened concepts description, focusing on nuances and input/output parameters.

"},{"location":"mlcubes/mlcube_metrics_WIP/#about-this-guide","title":"About this Guide","text":"

This guide describes the tasks, structure and input/output parameters of Metrics MLCube, allowing users at the end to be able to implement their own MedPerf-compatible MLCube for Benchmark purposes.

The guide starts with general advices, steps, and the required API for building these MLCubes. Subsequently, it will lead you through creating your MLCube using the Chest X-ray Data Preprocessor MLCube as a practical example.

Note: As the Dataset Owner would share the output of your metrics evaluation with you as Benchmark Owner, ensure that your metrics are not too specific and do not reveal any Personally Identifiable Information (PII) or other confidential data (including dataset statistics) - otherwise, no Dataset Owners would agree to participate in your benchmark.

"},{"location":"mlcubes/mlcube_metrics_WIP/#before-building-the-mlcube","title":"Before Building the MLCube","text":"

Your MLCube must implement an evaluate command that calculates your metrics.

It's assumed that you as Benchmark Owner already have:

  • Some raw data.
  • Implemented Data Preparator and a Baseline Model MLCubes as they are foundational to Benchmark pipeline.
  • Model predictions are stored at some my_model_predictions/ folder.
  • A working implementation of metric calculations.

This guide will help you encapsulate your preparation code within an MLCube. Make sure you extracted metric calculation logic, so it can be executed independently.

"},{"location":"mlcubes/mlcube_metrics_WIP/#required-api","title":"Required API","text":"

During execution, the evaluation command will receive specific parameters. While you are flexible in code implementation, keep in mind that your implementation will receive the following input arguments:

  • predictions: the path to the folder containing your predictions (read-only).
  • labels: the path to the folder containing transformed ground truth labels (read-only).
  • Any other optional extra params that you attach to the MLCube, such as parameters file. Note: these extra parameters contain values defined by you, the MLCube owner, not the users' data.
  • output_path: path to .yaml file where your code should write down calculated metrics.
"},{"location":"mlcubes/mlcube_metrics_WIP/#build-your-own-mlcube","title":"Build Your Own MLCube","text":"

While this guide leads you through creating your own MLCube, you can always check a prebuilt example for a better understanding of how it works in an already implemented MLCube. The example is available here:

cd examples/chestxray_tutorial/metrics/\n

The guide uses this implementation to describe concepts.

"},{"location":"mlcubes/mlcube_metrics_WIP/#use-an-mlcube-template","title":"Use an MLCube Template","text":"

First, ensure you have MedPerf installed. Create a Metrics MLCube template by running the following command:

medperf mlcube create evaluator\n

You will be prompted to fill in some configuration options through the CLI. Below are the options and their default values:

project_name [Evaluator MLCube]: # (1)!\nproject_slug [evaluator_mlcube]: # (2)!\ndescription [Evaluator MLCube Template. Provided by MLCommons]: # (3)!\nauthor_name [John Smith]: # (4)!\naccelerator_count [0]: # (5)!\ndocker_image_name [docker/image:latest]: # (6)!\n
  1. Gives a Human-readable name to the MLCube Project.
  2. Determines how the MLCube root folder will be named.
  3. Gives a Human-readable description to the MLCube Project.
  4. Documents the MLCube implementation by specifying the author.
  5. Specifies how many GPUs should be visible by the MLCube.
  6. MLCubes use Docker containers under the hood. Here, you can provide an image tag for the image created by this MLCube. You should use a valid name that allows you to upload it to a Docker registry.

After filling the configuration options, the following directory structure will be generated:

.\n\u2514\u2500\u2500 data_preparator_mlcube\n    \u251c\u2500\u2500 mlcube\n    \u2502   \u251c\u2500\u2500 mlcube.yaml\n    \u2502   \u2514\u2500\u2500 workspace\n    \u2502       \u2514\u2500\u2500 parameters.yaml\n    \u2514\u2500\u2500 project\n        \u251c\u2500\u2500 Dockerfile\n        \u251c\u2500\u2500 mlcube.py\n        \u2514\u2500\u2500 requirements.txt\n
"},{"location":"mlcubes/mlcube_metrics_WIP/#the-project-folder","title":"The project Folder","text":"

This is where your metrics logic will live. It contains a standard Docker image project with a specific API for the entrypoint. mlcube.py contains the entrypoint and handles the evaluate task. Update this template with your code and bind your logic to specified command entry-point function. Refer to the Chest X-ray tutorial example for an example of how it should look:

mlcube.py
\"\"\"MLCube handler file\"\"\"\nimport typer\nimport yaml\nfrom metrics import calculate_metrics\napp = typer.Typer()\n@app.command(\"evaluate\")\ndef evaluate(\nlabels: str = typer.Option(..., \"--labels\"),\npredictions: str = typer.Option(..., \"--predictions\"),\nparameters_file: str = typer.Option(..., \"--parameters_file\"),\noutput_path: str = typer.Option(..., \"--output_path\"),\n):\nwith open(parameters_file) as f:\nparameters = yaml.safe_load(f)\ncalculate_metrics(labels, predictions, parameters, output_path)\n@app.command(\"hotfix\")\ndef hotfix():\n# NOOP command for typer to behave correctly. DO NOT REMOVE OR MODIFY\npass\nif __name__ == \"__main__\":\napp()\n
"},{"location":"mlcubes/mlcube_metrics_WIP/#the-mlcube-folder","title":"The mlcube Folder","text":"

This folder is primarily for configuring your MLCube and providing additional files the MLCube may interact with, such as parameters or model weights.

"},{"location":"mlcubes/mlcube_metrics_WIP/#mlcubeyaml-mlcube-configuration","title":"mlcube.yaml MLCube Configuration","text":"

The mlcube/mlcube.yaml file contains metadata and configuration of your mlcube. This file is already populated with the configuration you provided during the template creation step. There is no need to edit anything in this file except if you are specifying extra parameters to the commands.

mlcube.py
name: Classification Metrics\ndescription: MedPerf Tutorial - Metrics MLCube.\nauthors:\n- { name: MLCommons Medical Working Group }\nplatform:\naccelerator_count: 0\ndocker:\n# Image name\nimage: mlcommons/chestxray-tutorial-metrics:0.0.0\n# Docker build context relative to $MLCUBE_ROOT. Default is `build`.\nbuild_context: \"../project\"\n# Docker file name within docker build context, default is `Dockerfile`.\nbuild_file: \"Dockerfile\"\ntasks:\nevaluate:\n# Computes evaluation metrics on the given predictions and ground truths\nparameters:\ninputs:\n{\npredictions: predictions,\nlabels: labels,\nparameters_file: parameters.yaml,\n}\noutputs: { output_path: { type: \"file\", default: \"results.yaml\" } }\n

All paths are relative to mlcube/workspace/ folder.

To set up additional inputs, add a key-value pair in the task's inputs dictionary:

mlcube.yaml
...\nprepare:\nparameters:\ninputs:\n{\n          predictions: predictions,\n          labels: labels,\n          parameters_file: parameters.yaml,\n          some_additional_file_with_weights: additional_files/my_weights.zip\n}\noutputs: { output_path: { type: \"file\", default: \"results.yaml\" } }\n...\n

Considering the note about path locations, this new file should be stored at mlcube/workspace/additional_files/my_weights.zip.

"},{"location":"mlcubes/mlcube_metrics_WIP/#parameters","title":"Parameters","text":"

Your metrics evaluation logic might depend on certain parameters (e.g., proba threshold for classifying predictions). It is generally better to pass such parameters when running the MLCube, rather than hardcoding them. This can be done via a parameters.yaml file that is passed to the MLCube. You can parse this file in the mlcube.py file and pass its contents to your logic.

This file should be placed in the mlcube/workspace folder.

"},{"location":"mlcubes/mlcube_metrics_WIP/#build-your-mlcube","title":"Build Your MLCube","text":"

After you follow the previous sections and fulfill the image with your logic, the MLCube is ready to be built and run. Run the command below to build the MLCube. Ensure you are in the mlcube/ subfolder of your Evaluator.

mlcube configure -Pdocker.build_strategy=always\n

This command builds your Docker image and prepares the MLCube for use.

"},{"location":"mlcubes/mlcube_metrics_WIP/#run-your-mlcube","title":"Run Your MLCube","text":"

MedPerf will take care of running your MLCube. However, it's recommended to test the MLCube alone before using it with MedPerf for better debugging.

To run the MLCube, use the command below. Ensure you are located in the mlcube/ subfolder of your Data Preparator.

mlcube run --task evaluate predictions=<path_to_predictions> \\\nlabels=<path_to_transformed_labels> \\\noutput_path=<path_to_yaml_file_to_save>\n

Relative paths

Keep in mind that though we are running tasks from mlcube/, all the paths should be absolute or relative to mlcube/workspace/.

Default values

Default values are set for every path parameter, allowing for their omission in commands. For example, in the discussed Chest X-Ray example, the predictions input is defined as follows:

...\ninputs:\n{\npredictions: predictions,\nlabels: labels,\n}\n...\n

If this parameter is omitted (e.g., running MLCube with default parameters by mlcube run --task evaluate), it's assumed that predictions are stored in the mlcube/workspace/predictions/ folder.

"},{"location":"mlcubes/mlcube_metrics_WIP/#using-the-example-with-gpus","title":"Using the Example with GPUs","text":"

The provided example codebase runs only on CPU. You can modify it to pass a GPU inside Docker image if your code utilizes it.

The general instructions for building an MLCube to work with a GPU are the same as the provided instructions, but with the following slight modifications:

  • Enter a number different than 0 for the accelerator_count that you will be prompted with when creating the MLCube template or modify platform.accelerator_count value of mlcube.yaml configuration.
  • Inside the docker section of the mlcube.yaml, add a key value pair: gpu_args: --gpus=all. These gpu_args will be passed to docker run command by MLCube. You may add more than just --gpus=all.
  • Make sure you install the required GPU dependencies in the docker image. For instance, this may be done by simply modifying the pip dependencies in the requirements.txt file to download pytorch with cuda, or by changing the base image of the dockerfile.
"},{"location":"mlcubes/mlcube_models/","title":"Model MLCube","text":""},{"location":"mlcubes/mlcube_models/#introduction","title":"Introduction","text":"

This is one of the three guides that help the user build MedPerf-compatible MLCubes. The other two guides are for building a Data Preparator MLCube and a Metrics MLCube. Together, the three MLCubes examples constitute a complete benchmark workflow for the task of thoracic disease detection from Chest X-rays.

"},{"location":"mlcubes/mlcube_models/#about-this-guide","title":"About this Guide","text":"

This guide will help users familiarize themselves with the expected interface of the Model MLCube and gain a comprehensive understanding of its components. By following this walkthrough, users will gain insights into the structure and organization of a Model MLCube, allowing them at the end to be able to implement their own MedPerf-compatible Model MLCube.

The guide will start by providing general advice, steps, and hints on building these MLCubes. Then, an example will be presented through which the provided guidance will be applied step-by-step to build a Chest X-ray classifier MLCube. The final MLCube code can be found here.

"},{"location":"mlcubes/mlcube_models/#before-building-the-mlcube","title":"Before Building the MLCube","text":"

It is assumed that you already have a working code that runs inference on data and generates predictions, and what you want to accomplish through this guide is to wrap your inference code within an MLCube.

  • Make sure you decouple your inference logic from the other machine learning common pipelines (e.g.; training, metrics, ...).
  • Your inference logic can be written in any structure, can be split into any number of files, can represent any number of inference stages, etc..., as long as the following hold:
    • The whole inference flow can be invoked by a single command/function.
    • This command/function has at least the following arguments:
      • A string representing a path that points to all input data records
      • A string representing a path that points to the desired output directory where the predictions will be stored.
  • Your inference logic should not alter the input files and folders.
  • Your inference logic should expect the input data in a certain structure. This is usually determined by following the specifications of the benchmark you want to participate in.
  • Your inference logic should save the predictions in the output directory in a certain structure. This is usually determined by following the specifications of the benchmark you want to participate in.
"},{"location":"mlcubes/mlcube_models/#use-an-mlcube-template","title":"Use an MLCube Template","text":"

MedPerf provides MLCube templates. You should start from a template for faster implementation and to build MLCubes that are compatible with MedPerf.

First, make sure you have MedPerf installed. You can create a model MLCube template by running the following command:

medperf mlcube create model\n

You will be prompted to fill in some configuration options through the CLI. Below are the options and their default values:

project_name [Model MLCube]: # (1)!\nproject_slug [model_mlcube]: # (2)!\ndescription [Model MLCube Template. Provided by MLCommons]: # (3)!\nauthor_name [John Smith]: # (4)!\naccelerator_count [0]: # (5)!\ndocker_image_name [docker/image:latest]: # (6)!\n
  1. Gives a Human-readable name to the MLCube Project.
  2. Determines how the MLCube root folder will be named.
  3. Gives a Human-readable description to the MLCube Project.
  4. Documents the MLCube implementation by specifying the author.
  5. Indicates how many GPUs should be visible by the MLCube.
  6. MLCubes use Docker containers under the hood. Here, you can provide an image tag to the image that will be created by this MLCube. You should use a valid name that allows you to upload it to a Docker registry.

After filling the configuration options, the following directory structure will be generated:

.\n\u2514\u2500\u2500 model_mlcube\n    \u251c\u2500\u2500 mlcube\n    \u2502   \u251c\u2500\u2500 mlcube.yaml\n    \u2502   \u2514\u2500\u2500 workspace\n    \u2502       \u2514\u2500\u2500 parameters.yaml\n    \u2514\u2500\u2500 project\n        \u251c\u2500\u2500 Dockerfile\n        \u251c\u2500\u2500 mlcube.py\n        \u2514\u2500\u2500 requirements.txt\n

The next sections will go through the contents of this directory in details and customize it.

"},{"location":"mlcubes/mlcube_models/#the-project-folder","title":"The project folder","text":"

This is where your inference logic will live. This folder initially contains three files as shown above. The upcoming sections will cover their use in details.

The first thing to do is put your code files in this folder.

"},{"location":"mlcubes/mlcube_models/#how-will-the-mlcube-identify-your-code","title":"How will the MLCube identify your code?","text":"

This is done through the mlcube.py file. This file defines the interface of the MLCube and should be linked to your inference logic.

mlcube.py
\"\"\"MLCube handler file\"\"\"\nimport typer\napp = typer.Typer()\n@app.command(\"infer\")\ndef infer(\ndata_path: str = typer.Option(..., \"--data_path\"),\nparameters_file: str = typer.Option(..., \"--parameters_file\"),\noutput_path: str = typer.Option(..., \"--output_path\"),\n# Provide additional parameters as described in the mlcube.yaml file\n# e.g. model weights:\n# weights: str = typer.Option(..., \"--weights\"),\n):\n# Modify the prepare command as needed\nraise NotImplementedError(\"The evaluate method is not yet implemented\")\n@app.command(\"hotfix\")\ndef hotfix():\n# NOOP command for typer to behave correctly. DO NOT REMOVE OR MODIFY\npass\nif __name__ == \"__main__\":\napp()\n

As shown above, this file exposes a command infer. It's basic arguments are the input data path, the output predictions path, and a parameters file path.

The parameters file, as will be explained in the upcoming sections, gives flexibility to your MLCube. For example, instead of hardcoding the inference batch size in the code, it can be configured by passing a parameters file to your MLCube which contains its value. This way, your same MLCube can be reused with multiple batch sizes by just changing the input parameters file.

You should ignore the hotfix command as described in the file.

The infer command will be automatically called by the MLCube when it's built and run. This command should call your inference logic. Make sure you replace its contents with a code that calls your inference logic. This could be by importing a function from your code files and calling it with the necessary arguments.

"},{"location":"mlcubes/mlcube_models/#prepare-your-dockerfile","title":"Prepare your Dockerfile","text":"

The MLCube will execute a docker image whose entrypoint is mlcube.py. The MLCube will first build this image from the Dockerfile specified in the project folder. You can customize the Dockerfile however you want as long as the entrypoint is runs the mlcube.py file

Make sure you include in your Dockerfile any system dependency your code depends on. It is also common to have pip dependencies, make sure you install them in the Dockerfile as well.

Below is the docker file provided in the template:

Dockerfile
FROM python:3.9.16-slim\nCOPY ./requirements.txt /mlcube_project/requirements.txt RUN pip3 install --no-cache-dir -r /mlcube_project/requirements.txt\n\nENV LANG C.UTF-8\n\nCOPY . /mlcube_project\n\nENTRYPOINT [\"python3\", \"/mlcube_project/mlcube.py\"]\n

As shown above, this docker file makes sure python is available by using the python base image, installs pip dependencies using the requirements.txt file, and sets the entrypoint to run mlcube.py. Note that the MLCube tool will invoke the Docker build command from the project folder, so it will copy all your files found in the project to the Docker image.

"},{"location":"mlcubes/mlcube_models/#the-mlcube-folder","title":"The mlcube folder","text":"

This folder is mainly for configuring your MLCube and providing additional files the MLCube may interact with, such as parameters or model weights.

"},{"location":"mlcubes/mlcube_models/#include-additional-input-files","title":"Include additional input files","text":""},{"location":"mlcubes/mlcube_models/#parameters","title":"parameters","text":"

Your inference logic may depend on some parameters (e.g. inference batch size). It is usually a more favorable design to not hardcode such parameters, but instead pass them when running the MLCube. This can be done by having a parameters.yaml file as an input to the MLCube. This file will be available to the infer command described before. You can parse this file in the mlcube.py file and pass its contents to your code.

This file should be placed in the mlcube/workspace folder.

"},{"location":"mlcubes/mlcube_models/#model-weights","title":"model weights","text":"

It is a good practice not to ship your model weights within the docker image to reduce the image size and provide flexibility of running the MLCube with different model weights. To do this, model weights path should be provided as a separate parameter to the MLCube. You should place your model weights in a folder named additional_files inside the mlcube/workspace folder. This is how MedPerf expects any additional input to your MLCube beside the data path and the paramters file.

After placing your model weights in mlcube/workspace/additional_files, you have to modify two files:

  • mlcube.py: add an argument to the infer command which will correspond to the path of your model weights. Remember also to pass this argument to your inference logic.
  • mlcube.yaml: The next section introduces this file and describes it in details. You should add your extra input arguments to this file as well, as described below.
"},{"location":"mlcubes/mlcube_models/#configure-your-mlcube","title":"Configure your MLCube","text":"

The mlcube.yaml file contains metadata and configuration of your mlcube. This file was already populated with the configuration you provided during the step of creating the template. There is no need to edit anything in this file except if you are specifying extra parameters to the infer command (e.g., model weights as described in the previous section).

You will be modifying the tasks section of the mlcube.yaml file in order to account for extra additional inputs:

mlcube.yaml
tasks:\ninfer:\n# Computes predictions on input data\nparameters:\ninputs: {\n          data_path: data/,\n          parameters_file: parameters.yaml,\n# Feel free to include other files required for inference.\n# These files MUST go inside the additional_files path.\n# e.g. model weights\n          # weights: additional_files/weights.pt,\n}\noutputs: { output_path: { type: directory, default: predictions } }\n

As hinted by the comments as well, you can add the additional parameters by specifying an extra key-value pair in the inputs dictionary of the infer task.

"},{"location":"mlcubes/mlcube_models/#build-your-mlcube","title":"Build your MLCube","text":"

After you follow the previous sections, the MLCube is ready to be built and run. Run the command below to build the MLCube. Make sure you are in the folder model_mlcube/mlcube.

mlcube configure -Pdocker.build_strategy=always\n

This command will build your docker image and make the MLCube ready to use.

"},{"location":"mlcubes/mlcube_models/#run-your-mlcube","title":"Run your MLCube","text":"

MedPerf will take care of running your MLCube. However, it's recommended to test the MLCube alone before using it with MedPerf for better debugging.

Use the command below to run the MLCube. Make sure you are in the folder model_mlcube/mlcube.

mlcube run --task infer data_path=<absolute path to input data> output_path=<absolute path to a folder where predictions will be saved>\n
"},{"location":"mlcubes/mlcube_models/#a-working-example","title":"A Working Example","text":"

Assume you have the codebase below. This code can be used to predict thoracic diseases based on Chest X-ray data. The classification task is modeled as a multi-label classification class.

models.py
\"\"\"\nTaken from MedMNIST/MedMNIST.\n\"\"\"\nimport torch.nn as nn\nclass SimpleCNN(nn.Module):\ndef __init__(self, in_channels, num_classes):\nsuper(SimpleCNN, self).__init__()\nself.layer1 = nn.Sequential(\nnn.Conv2d(in_channels, 16, kernel_size=3), nn.BatchNorm2d(16), nn.ReLU()\n)\nself.layer2 = nn.Sequential(\nnn.Conv2d(16, 16, kernel_size=3),\nnn.BatchNorm2d(16),\nnn.ReLU(),\nnn.MaxPool2d(kernel_size=2, stride=2),\n)\nself.layer3 = nn.Sequential(\nnn.Conv2d(16, 64, kernel_size=3), nn.BatchNorm2d(64), nn.ReLU()\n)\nself.layer4 = nn.Sequential(\nnn.Conv2d(64, 64, kernel_size=3), nn.BatchNorm2d(64), nn.ReLU()\n)\nself.layer5 = nn.Sequential(\nnn.Conv2d(64, 64, kernel_size=3, padding=1),\nnn.BatchNorm2d(64),\nnn.ReLU(),\nnn.MaxPool2d(kernel_size=2, stride=2),\n)\nself.fc = nn.Sequential(\nnn.Linear(64 * 4 * 4, 128),\nnn.ReLU(),\nnn.Linear(128, 128),\nnn.ReLU(),\nnn.Linear(128, num_classes),\n)\ndef forward(self, x):\nx = self.layer1(x)\nx = self.layer2(x)\nx = self.layer3(x)\nx = self.layer4(x)\nx = self.layer5(x)\nx = x.view(x.size(0), -1)\nx = self.fc(x)\nreturn x\n
data_loader.py
import numpy as np\nimport torchvision.transforms as transforms\nimport os\nfrom torch.utils.data import Dataset\nclass CustomImageDataset(Dataset):\ndef __init__(self, data_path):\nself.transform = transforms.Compose(\n[transforms.ToTensor(), transforms.Normalize(mean=[0.5], std=[0.5])]\n)\nself.files = os.listdir(data_path)\nself.data_path = data_path\ndef __len__(self):\nreturn len(self.files)\ndef __getitem__(self, idx):\nimg_path = os.path.join(self.data_path, self.files[idx])\nimage = np.load(img_path, allow_pickle=True)\nimage = self.transform(image)\nfile_id = self.files[idx].strip(\".npy\")\nreturn image, file_id\n
infer.py
import torch\nfrom models import SimpleCNN\nfrom tqdm import tqdm\nfrom torch.utils.data import DataLoader\nfrom data_loader import CustomImageDataset\nfrom pprint import pprint\ndata_path = \"path/to/data/folder\"\nweights = \"path/to/weights.pt\"\nin_channels = 1\nnum_classes = 14\nbatch_size = 5\n# load model\nmodel = SimpleCNN(in_channels=in_channels, num_classes=num_classes)\nmodel.load_state_dict(torch.load(weights))\nmodel.eval()\n# load prepared data\ndataset = CustomImageDataset(data_path)\ndataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)\n# inference\npredictions_dict = {}\nwith torch.no_grad():\nfor images, files_ids in tqdm(dataloader):\noutputs = model(images)\noutputs = torch.nn.Sigmoid()(outputs)\noutputs = outputs.detach().numpy()\nfor file_id, output in zip(files_ids, outputs):\npredictions_dict[file_id] = output\npprint(predictions_dict)\n

Throughout the next sections, this code will be wrapped within an MLCube.

"},{"location":"mlcubes/mlcube_models/#before-building-the-mlcube_1","title":"Before Building the MLCube","text":"

The guidlines listed previously in this section will now be applied to the given codebase. Assume that you were instructed by the benchmark you are participating with to have your MLCube interface as follows:

  • The MLCube should expect the input data folder to contain a list of images as numpy files.
  • The MLCube should save the predictions in a single JSON file as key-value pairs of image file ID and its corresponding prediction. A prediction should be a vector of length 14 (number of classes) and has to be the output of the Sigmoid activation layer.

It is important to make sure that your MLCube will output an expected predictions format and consume a defined data format, since it will be used in a benchmarking pipeline whose data input is fixed and whose metrics calculation logic expects a fixed predictions format.

Considering the codebase above, here are the things that should be done before proceeding to build the MLCube:

  • infer.py only prints predictions but doesn't store them. This has to be changed.
  • infer.py hardcodes some parameters (num_classes, in_channels, batch_size) as well as the path to the trained model weights. Consider making these items configurable parameters. (This is optional but recommended)
  • Consider refactoring infer.py to be a function so that is can be easily called by mlcube.py.

The other files models.py and data_loader.py seem to be good already. The data loader expects a folder containing a list of numpy arrays, as instructed.

Here is the modified version of infer.py according to the points listed above:

infer.py (Modified)
import torch\nimport os\nfrom models import SimpleCNN\nfrom tqdm import tqdm\nfrom torch.utils.data import DataLoader\nfrom data_loader import CustomImageDataset\nimport json\ndef run_inference(data_path, parameters, output_path, weights):\nin_channels = parameters[\"in_channels\"]\nnum_classes = parameters[\"num_classes\"]\nbatch_size = parameters[\"batch_size\"]\n# load model\nmodel = SimpleCNN(in_channels=in_channels, num_classes=num_classes)\nmodel.load_state_dict(torch.load(weights))\nmodel.eval()\n# load prepared data\ndataset = CustomImageDataset(data_path)\ndataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)\n# inference\npredictions_dict = {}\nwith torch.no_grad():\nfor images, files_ids in tqdm(dataloader):\noutputs = model(images)\noutputs = torch.nn.Sigmoid()(outputs)\noutputs = outputs.detach().numpy().tolist()\nfor file_id, output in zip(files_ids, outputs):\npredictions_dict[file_id] = output\n# save\npreds_file = os.path.join(output_path, \"predictions.json\")\nwith open(preds_file, \"w\") as f:\njson.dump(predictions_dict, f, indent=4)\n
"},{"location":"mlcubes/mlcube_models/#create-an-mlcube-template","title":"Create an MLCube Template","text":"

Assuming you installed MedPerf, run the following:

medperf mlcube create model\n

You will be prompted to fill in the configuration options. Use the following configuration as a reference:

project_name [Model MLCube]: Custom CNN Classification Model\nproject_slug [model_mlcube]: model_custom_cnn\ndescription [Model MLCube Template. Provided by MLCommons]: MedPerf Tutorial - Model MLCube.\nauthor_name [John Smith]: <use your name>\naccelerator_count [0]: 0\ndocker_image_name [docker/image:latest]: repository/model-tutorial:0.0.0\n

Note

This example is built to be used with a CPU. See the last section to know how to configure this example with a GPU.

Note that docker_image_name is arbitrarily chosen. Use a valid docker image.

"},{"location":"mlcubes/mlcube_models/#move-your-codebase","title":"Move your Codebase","text":"

Move the three files of the codebase to the project folder. The directory tree will then look like this:

.\n\u2514\u2500\u2500 model_custom_cnn\n    \u251c\u2500\u2500 mlcube\n    \u2502   \u251c\u2500\u2500 mlcube.yaml\n    \u2502   \u2514\u2500\u2500 workspace\n    \u2502       \u2514\u2500\u2500 parameters.yaml\n    \u2514\u2500\u2500 project\n        \u251c\u2500\u2500 Dockerfile\n        \u251c\u2500\u2500 mlcube.py\n        \u251c\u2500\u2500 models.py\n        \u251c\u2500\u2500 data_loader.py\n        \u251c\u2500\u2500 infer.py\n        \u2514\u2500\u2500 requirements.txt\n
"},{"location":"mlcubes/mlcube_models/#add-your-parameters-and-model-weights","title":"Add your parameters and model weights","text":"

Since num_classes, in_channels, and batch_size are now parametrized, they should be defined in workspace/parameters.yaml. Also, the model weights should be placed inside workspace/additional_files.

"},{"location":"mlcubes/mlcube_models/#add-parameters","title":"Add parameters","text":"

Modify parameters.yaml to include the following:

parameters.yaml
in_channels: 1\nnum_classes: 14\nbatch_size: 5\n
"},{"location":"mlcubes/mlcube_models/#add-model-weights","title":"Add model weights","text":"

Download the following model weights to use in this example: Click here to Download

Extract the file to workspace/additional_files. The directory tree should look like this:

.\n\u2514\u2500\u2500 model_custom_cnn\n    \u251c\u2500\u2500 mlcube\n    \u2502   \u251c\u2500\u2500 mlcube.yaml\n    \u2502   \u2514\u2500\u2500 workspace\n    \u2502       \u251c\u2500\u2500 additional_files\n    \u2502       \u2502   \u2514\u2500\u2500 cnn_weights.pth\n    \u2502       \u2514\u2500\u2500 parameters.yaml\n    \u2514\u2500\u2500 project\n        \u251c\u2500\u2500 Dockerfile\n        \u251c\u2500\u2500 mlcube.py\n        \u251c\u2500\u2500 models.py\n        \u251c\u2500\u2500 data_loader.py\n        \u251c\u2500\u2500 infer.py\n        \u2514\u2500\u2500 requirements.txt\n
"},{"location":"mlcubes/mlcube_models/#modify-mlcubepy","title":"Modify mlcube.py","text":"

Next, the inference logic should be triggered from mlcube.py. The parameters_file will be read in mlcube.py and passed as a dictionary to the inference logic. Also, an extra parameter weights is added to the function signature which will correspond to the model weights path. See below the modified mlcube.py file.

mlcube.py (Modified)
\"\"\"MLCube handler file\"\"\"\nimport typer\nimport yaml\nfrom infer import run_inference\napp = typer.Typer()\n@app.command(\"infer\")\ndef infer(\ndata_path: str = typer.Option(..., \"--data_path\"),\nparameters_file: str = typer.Option(..., \"--parameters_file\"),\noutput_path: str = typer.Option(..., \"--output_path\"),\nweights: str = typer.Option(..., \"--weights\"),\n):\nwith open(parameters_file) as f:\nparameters = yaml.safe_load(f)\nrun_inference(data_path, parameters, output_path, weights)\n@app.command(\"hotfix\")\ndef hotfix():\n# NOOP command for typer to behave correctly. DO NOT REMOVE OR MODIFY\npass\nif __name__ == \"__main__\":\napp()\n
"},{"location":"mlcubes/mlcube_models/#prepare-the-dockerfile","title":"Prepare the Dockerfile","text":"

The provided Dockerfile in the template is enough and preconfigured to download pip dependencies from the requirements.txt file. All that is needed is to modify the requirements.txt file to include the project's pip dependencies.

requirements.txt
typer==0.9.0\nnumpy==1.24.3\nPyYAML==6.0\ntorch==2.0.1\ntorchvision==0.15.2\ntqdm==4.65.0\n--extra-index-url https://download.pytorch.org/whl/cpu\n
"},{"location":"mlcubes/mlcube_models/#modify-mlcubeyaml","title":"Modify mlcube.yaml","text":"

Since the extra parameter weights was added to the infer task in mlcube.py, this has to be reflected on the defined MLCube interface in the mlcube.yaml file. Modify the tasks section to include an extra input parameter: weights: additional_files/cnn_weights.pth.

Tip

The MLCube tool interprets these paths as relative to the workspace.

The tasks section will then look like this:

mlcube.yaml
tasks:\ninfer:\n# Computes predictions on input data\nparameters:\ninputs:\n{\n          data_path: data/,\n          parameters_file: parameters.yaml,\n          weights: additional_files/cnn_weights.pth,\n}\noutputs: { output_path: { type: directory, default: predictions } }\n
"},{"location":"mlcubes/mlcube_models/#build-your-mlcube_1","title":"Build your MLCube","text":"

Run the command below to create the MLCube. Make sure you are in the folder model_custom_cnn/mlcube.

mlcube configure -Pdocker.build_strategy=always\n

This command will build your docker image and make the MLCube ready to use.

Tip

Run docker image ls to see your built Docker image.

"},{"location":"mlcubes/mlcube_models/#run-your-mlcube_1","title":"Run your MLCube","text":"

Download a sample data to run on: Click here to Download

Extract the data. You will get a folder sample_prepared_data containing a list chest X-ray images as numpy arrays.

Use the command below to run the MLCube. Make sure you are in the the folder model_custom_cnn/mlcube.

mlcube run --task infer data_path=<absolute path to `sample_prepared_data`> output_path=<absolute path to a folder where predictions will be saved>\n
"},{"location":"mlcubes/mlcube_models/#using-the-example-with-gpus","title":"Using the Example with GPUs","text":"

The provided example codebase runs only on CPU. You can modify it to have pytorch run inference on a GPU.

The general instructions for building an MLCube to work with a GPU are the same as the provided instructions, but with the following slight modifications:

  • Use a number different than 0 for the accelerator_count that you will be prompted with when creating the MLCube template.
  • Inside the docker section of the mlcube.yaml, add a key value pair: gpu_args: --gpus=all. These gpu_args will be passed to docker run under the hood by MLCube. You may add more than just --gpus=all.
  • Make sure you install the required GPU dependencies in the docker image. For instance, this may be done by simply modifying the pip dependencies in the requirements.txt file to download pytorch with cuda, or by changing the base image of the dockerfile.
"},{"location":"mlcubes/mlcubes/","title":"MedPerf MLCubes","text":"

MLCube is a set of common conventions for creating Machine Learning (ML) software that can \"plug-and-play\" on many different systems. It is basically a container image with a simple interface and the correct metadata that allows researchers and developers to easily share and experiment with ML pipelines.

You can read more about MLCubes here.

In MedPerf, MLCubes are required for creating the three technical components of a benchmarking experiment: the data preparation flow, the model inference flow, and the evaluation flow. A Benchmark Committee will be required to create three MLCubes that implement these components. A Model Owner will be required to wrap their model code within an MLCube in order to submit it to the MedPerf server and participate in a benchmark.

MLCubes are general-purpose. MedPerf defines three specific design types of MLCubes according to their purpose: The Data Preparator MLCube, the Model MLCube, and the Metrics MLCube. Each type has a specific MLCube task configuration that defines the MLCube's interface. Users need to follow these design specs when building their MLCubes to be conforming with MedPerf. We provide below a high-level description of each MLCube type and a link to a guide for building an example for each type.

"},{"location":"mlcubes/mlcubes/#data-preparator-mlcube","title":"Data Preparator MLCube","text":"

The Data Preparator MLCube is used to prepare the data for executing the benchmark. Ideally, it can receive different data standards for the task at hand, transforming them into a single, unified standard. Additionally, it ensures the quality and compatibility of the data and computes statistics and metadata for registration purposes.

This MLCube's interface should expose the following tasks:

  • Prepare: Transforms the input data into the expected output data standard. It receives as input the location of the original data, as well as the location of the labels, and outputs the prepared dataset and accompanying labels.

  • Sanity check: Ensures the integrity of the prepared data. It may check for anomalies and data corruption (e.g. blank images, empty test cases). It constitutes a set of conditions the prepared data should comply with.

  • Statistics: Computes statistics on the prepared data.

Check this guide on how to create a Data Preparation MLCube.

"},{"location":"mlcubes/mlcubes/#model-mlcube","title":"Model MLCube","text":"

The model MLCube contains a pre-trained machine learning model that is going to be evaluated by the benchmark. It's interface should expose the following task:

  • Infer: Obtains predictions on the prepared data. It receives as input the location of the prepared data and outputs the predictions.

Check this guide on how to create a Model MLCube.

"},{"location":"mlcubes/mlcubes/#metricsevaluator-mlcube","title":"Metrics/Evaluator MLCube","text":"

The Metrics MLCube is used for computing metrics on the model predictions by comparing them against the provided labels. It's interface should expose the following task:

  • Evaluate: Computes the metrics. It receives as input the location of the predictions and the location of the prepared data labels and generates a yaml file containing the metrics.

Check this guide on how to create a Metrics MLCube.

"},{"location":"mlcubes/shared/build/","title":"Build","text":""},{"location":"mlcubes/shared/build/#building-a-no-such-element-dict-objectname","title":"Building a {{ no such element: dict object['name'] }}","text":"

The following section will describe how you can create a {{ no such element: dict object['name'] }} from scratch. This documentation goes through the set of commands provided to help during this process, as well as the contents of a {{ no such element: dict object['name'] }}.

"},{"location":"mlcubes/shared/build/#setup","title":"Setup","text":"

MedPerf provides some cookiecutter templates for all the related MLCubes. Additionally, it provides commands to easily retreive and use these templates. For that, you need to make sure MedPerf is installed. If you haven not done so, please follow the steps below:

  1. Clone the repository:

    git clone https://github.com/mlcommons/medperf\ncd medperf\n

  2. Install the MedPerf CLI:

    pip install -e cli\n

  3. If you have not done so, create a folder for keeping all MLCubes created in this tutorial:

    mkdir tutorial\ncd tutorial\n

  4. Create a {{ no such element: dict object['name'] }} through MedPerf:

    medperf mlcube create {{ no such element: dict object['slug'] }}\n
    You should be prompted to fill in some configuration options through the CLI. An example of some good options to provide for this specific task is presented below:

"},{"location":"mlcubes/shared/contents/","title":"Contents","text":""},{"location":"mlcubes/shared/contents/#contents","title":"Contents","text":"

Let's have a look at what the previous command generated. First, lets look at the whole folder structure:

tree 

"},{"location":"mlcubes/shared/cookiecutter/","title":"Cookiecutter","text":"

Note

MedPerf is running CookieCutter under the hood. This medperf command provides additional arguments for handling different scenarios. You can see more information on this by running medperf mlcube create --help

"},{"location":"mlcubes/shared/docker_file/","title":"Docker file","text":""},{"location":"mlcubes/shared/docker_file/#projectdockerfile","title":"project/Dockerfile","text":"

MLCubes rely on containers to work. By default, Medperf provides a functional Dockerfile, which uses ubuntu:18.0.4 and python3.6. This Dockerfile handles all the required procedures to install your project and redirect commands to the project/mlcube.py file. You can modify as you see fit, as long as the entry point behaves as a CLI, as described before.

Running Docker MLCubes with Singularity

If you are building a Docker MLCube and expect it to be also run using Singularity, you need to keep in mind that Singularity containers built from Docker images ignore the WORKDIR instruction if used in Dockerfiles. Make sure you also follow their best practices for writing Singularity-compatible Dockerfiles.

"},{"location":"mlcubes/shared/execute/","title":"Execute","text":""},{"location":"mlcubes/shared/execute/#execute","title":"Execute","text":"

Now its time to run our own implementation. We won't go into much detail, since we covered the basics before. But, here are the commands you can run to build and run your MLCube.

  1. Go to the MLCube folder. For this, assuming you are in the root of the {{ no such element: dict object['slug'] }}_mlcube, run
    cd mlcube\n
  2. Build the Docker image using the shortcuts provided by MLCubse. Here is how you can do it:

    mlcube configure -Pdocker.build_strategy=always # (1)!\n

    1. MLCube by default will look for the image on Docker hub or locally instead of building it. Providing Pdocker.build_strategy=always enforces MLCube to build the image from source.
"},{"location":"mlcubes/shared/hello_world/","title":"Hello world","text":"

In order to provide a basic example of how Medperf MLCubes work under the hood, a toy Hello World benchmark is provided. This benchmark implements a pipeline for ingesting people's names and generating greetings for those names given some criteria. Although this is not the most scientific example, it provides a clear idea of all the pieces required to implement your MLCubes for Medperf.

You can find the {{ no such element: dict object['name'] }} code here

"},{"location":"mlcubes/shared/hotfix/","title":"Hotfix","text":"

What is the hotfix function inside mlcube.py?

To summarize, this issue is benign and can be safely ignored. It prevents a potential issue with the CLI and does not require further action.

If you use the typer/click library for your command-line interface (CLI) and have only one @app.command, the command line may not be parsed as expected by mlcube. This is due to a known issue that can be resolved by adding more than one task to the mlcube interface.

To avoid a potential issue with the CLI, we add a dummy typer command to our model cubes that only have one task. If you're not using typer/click, you don't need this dummy command.

"},{"location":"mlcubes/shared/requirements/","title":"Requirements","text":""},{"location":"mlcubes/shared/requirements/#projectrequirementstxt","title":"project/requirements.txt","text":"

The provided MLCube template assumes your project is python based. Because of this, it provides a requirements.txt file to specify the dependencies to run your project. This file is automatically used by the Dockerfile to install and set up your project. Since some dependencies are necessary, let's add them to the file:

"},{"location":"mlcubes/shared/setup/","title":"Setup","text":""},{"location":"mlcubes/shared/setup/#how-to-run","title":"How to run","text":"

Before digging into the code, let's try manually running the {{ no such element: dict object['name'] }}. During this process, it should be possible to see how MLCube interacts with the folders in the workspace and what is expected to happen during each step:

"},{"location":"mlcubes/shared/setup/#setup","title":"Setup","text":"
  1. Clone the repository:

    git clone https://github.com/mlcommons/medperf\ncd medperf\n

  2. Install mlcube and mlcube-docker using pip:

    pip install mlcube mlcube-docker\n

  3. Navigate to the HelloWorld directory within the examples folder with

    cd examples/HelloWorld\n

  4. Change to the current example's mlcube folder with

    cd {{ no such element: dict object['slug'] }}/mlcube\n

"},{"location":"reference/SUMMARY/","title":"SUMMARY","text":"
  • _version
  • account_management
    • account_management
    • token_storage
      • filesystem
      • keyring_
  • cli
  • commands
    • association
      • approval
      • association
      • list
      • priority
    • auth
      • auth
      • login
      • logout
      • status
      • synapse_login
    • benchmark
      • associate
      • benchmark
      • submit
    • compatibility_test
      • compatibility_test
      • run
      • utils
      • validate_params
    • dataset
      • associate
      • dataset
      • prepare
      • set_operational
      • submit
    • execution
    • list
    • mlcube
      • associate
      • create
      • mlcube
      • submit
    • profile
    • result
      • create
      • result
      • submit
    • storage
    • view
  • comms
    • auth
      • auth0
      • interface
      • local
      • token_verifier
    • entity_resources
      • resources
      • sources
        • direct
        • source
        • synapse
      • utils
    • factory
    • interface
    • rest
  • config
  • config_management
    • config_management
  • decorators
  • entities
    • benchmark
    • cube
    • dataset
    • interface
    • report
    • result
    • schemas
  • enums
  • exceptions
  • init
  • storage
    • utils
  • ui
    • cli
    • factory
    • interface
    • stdin
  • utils
"},{"location":"reference/_version/","title":"version","text":""},{"location":"reference/cli/","title":"Cli","text":""},{"location":"reference/cli/#cli.execute","title":"execute(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='UID of the desired benchmark'), data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), model_uid=typer.Option(..., '--model_uid', '-m', help='UID of model to execute'), approval=typer.Option(False, '-y', help='Skip approval step'), ignore_model_errors=typer.Option(False, '--ignore-model-errors', help='Ignore failing model cubes, allowing for possibly submitting partial results'), no_cache=typer.Option(False, '--no-cache', help='Ignore existing results. The experiment then will be rerun'))","text":"

Runs the benchmark execution step for a given benchmark, prepared dataset and model

Source code in cli/medperf/cli.py
@app.command(\"run\")\n@clean_except\ndef execute(\nbenchmark_uid: int = typer.Option(\n..., \"--benchmark\", \"-b\", help=\"UID of the desired benchmark\"\n),\ndata_uid: int = typer.Option(\n..., \"--data_uid\", \"-d\", help=\"Registered Dataset UID\"\n),\nmodel_uid: int = typer.Option(\n..., \"--model_uid\", \"-m\", help=\"UID of model to execute\"\n),\napproval: bool = typer.Option(False, \"-y\", help=\"Skip approval step\"),\nignore_model_errors: bool = typer.Option(\nFalse,\n\"--ignore-model-errors\",\nhelp=\"Ignore failing model cubes, allowing for possibly submitting partial results\",\n),\nno_cache: bool = typer.Option(\nFalse,\n\"--no-cache\",\nhelp=\"Ignore existing results. The experiment then will be rerun\",\n),\n):\n\"\"\"Runs the benchmark execution step for a given benchmark, prepared dataset and model\"\"\"\nresult = BenchmarkExecution.run(\nbenchmark_uid,\ndata_uid,\n[model_uid],\nignore_model_errors=ignore_model_errors,\nno_cache=no_cache,\n)[0]\nif result.id:  # TODO: use result.is_registered once PR #338 is merged\nconfig.ui.print(  # TODO: msg should be colored yellow\n\"\"\"An existing registered result for the requested execution has been\\n\n            found. If you wish to submit a new result for the same execution,\\n\n            please run the command again with the --no-cache option.\\n\"\"\"\n)\nelse:\nResultSubmission.run(result.generated_uid, approved=approval)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/config/","title":"Config","text":""},{"location":"reference/decorators/","title":"Decorators","text":""},{"location":"reference/decorators/#decorators.add_inline_parameters","title":"add_inline_parameters(func)","text":"

Decorator that adds common configuration options to a typer command

Parameters:

Name Type Description Default func Callable

function to be decorated

required

Returns:

Name Type Description Callable Callable

decorated function

Source code in cli/medperf/decorators.py
def add_inline_parameters(func: Callable) -> Callable:\n\"\"\"Decorator that adds common configuration options to a typer command\n    Args:\n        func (Callable): function to be decorated\n    Returns:\n        Callable: decorated function\n    \"\"\"\n# NOTE: changing parameters here should be accompanied\n#       by changing config.inline_parameters\n@merge_args(func)\ndef wrapper(\n*args,\nloglevel: str = typer.Option(\nconfig.loglevel,\n\"--loglevel\",\nhelp=\"Logging level [debug | info | warning | error]\",\n),\nprepare_timeout: int = typer.Option(\nconfig.prepare_timeout,\n\"--prepare_timeout\",\nhelp=\"Maximum time in seconds before interrupting prepare task\",\n),\nsanity_check_timeout: int = typer.Option(\nconfig.sanity_check_timeout,\n\"--sanity_check_timeout\",\nhelp=\"Maximum time in seconds before interrupting sanity_check task\",\n),\nstatistics_timeout: int = typer.Option(\nconfig.statistics_timeout,\n\"--statistics_timeout\",\nhelp=\"Maximum time in seconds before interrupting statistics task\",\n),\ninfer_timeout: int = typer.Option(\nconfig.infer_timeout,\n\"--infer_timeout\",\nhelp=\"Maximum time in seconds before interrupting infer task\",\n),\nevaluate_timeout: int = typer.Option(\nconfig.evaluate_timeout,\n\"--evaluate_timeout\",\nhelp=\"Maximum time in seconds before interrupting evaluate task\",\n),\ncontainer_loglevel: str = typer.Option(\nconfig.container_loglevel,\n\"--container-loglevel\",\nhelp=\"Logging level for containers to be run [debug | info | warning | error]\",\n),\nplatform: str = typer.Option(\nconfig.platform,\n\"--platform\",\nhelp=\"Platform to use for MLCube. [docker | singularity]\",\n),\ngpus: str = typer.Option(\nconfig.gpus,\n\"--gpus\",\nhelp=\"\"\"\n            What GPUs to expose to MLCube.\n            Accepted Values are:\\n\n            - \"\" or 0: to expose no GPUs (e.g.: --gpus=\"\")\\n\n            - \"all\": to expose all GPUs. (e.g.: --gpus=all)\\n\n            - an integer: to expose a certain number of GPUs. ONLY AVAILABLE FOR DOCKER\n            (e.g., --gpus=2 to expose 2 GPUs)\\n\n            - Form \"device=<id1>,<id2>\": to expose specific GPUs.\n            (e.g., --gpus=\"device=0,2\")\\n\"\"\",\n),\ncleanup: bool = typer.Option(\nconfig.cleanup,\n\"--cleanup/--no-cleanup\",\nhelp=\"Whether to clean up temporary medperf storage after execution\",\n),\n**kwargs,\n):\nreturn func(*args, **kwargs)\nreturn wrapper\n
"},{"location":"reference/decorators/#decorators.clean_except","title":"clean_except(func)","text":"

Decorator for handling errors. It allows logging and cleaning the project's directory before throwing the error.

Parameters:

Name Type Description Default func Callable

Function to handle for errors

required

Returns:

Name Type Description Callable Callable

Decorated function

Source code in cli/medperf/decorators.py
def clean_except(func: Callable) -> Callable:\n\"\"\"Decorator for handling errors. It allows logging\n    and cleaning the project's directory before throwing the error.\n    Args:\n        func (Callable): Function to handle for errors\n    Returns:\n        Callable: Decorated function\n    \"\"\"\n@functools.wraps(func)\ndef wrapper(*args, **kwargs):\ntry:\nlogging.info(f\"Running function '{func.__name__}'\")\nfunc(*args, **kwargs)\nexcept CleanExit as e:\nlogging.info(str(e))\nconfig.ui.print(str(e))\nsys.exit(e.medperf_status_code)\nexcept MedperfException as e:\nlogging.exception(e)\npretty_error(str(e))\nsys.exit(1)\nexcept Exception as e:\nlogging.error(\"An unexpected error occured. Terminating.\")\nlogging.exception(e)\nraise e\nfinally:\npackage_logs()\ncleanup()\nreturn wrapper\n
"},{"location":"reference/decorators/#decorators.configurable","title":"configurable(func)","text":"

Decorator that adds common configuration options to a typer command

Parameters:

Name Type Description Default func Callable

function to be decorated

required

Returns:

Name Type Description Callable Callable

decorated function

Source code in cli/medperf/decorators.py
def configurable(func: Callable) -> Callable:\n\"\"\"Decorator that adds common configuration options to a typer command\n    Args:\n        func (Callable): function to be decorated\n    Returns:\n        Callable: decorated function\n    \"\"\"\n# NOTE: changing parameters here should be accompanied\n#       by changing configurable_parameters\n@merge_args(func)\ndef wrapper(\n*args,\nserver: str = typer.Option(\nconfig.server, \"--server\", help=\"URL of a hosted MedPerf API instance\"\n),\nauth_class: str = typer.Option(\nconfig.auth_class,\n\"--auth_class\",\nhelp=\"Authentication interface to use [Auth0]\",\n),\nauth_domain: str = typer.Option(\nconfig.auth_domain, \"--auth_domain\", help=\"Auth0 domain name\"\n),\nauth_jwks_url: str = typer.Option(\nconfig.auth_jwks_url, \"--auth_jwks_url\", help=\"Auth0 Json Web Key set URL\"\n),\nauth_idtoken_issuer: str = typer.Option(\nconfig.auth_idtoken_issuer,\n\"--auth_idtoken_issuer\",\nhelp=\"Auth0 ID token issuer\",\n),\nauth_client_id: str = typer.Option(\nconfig.auth_client_id, \"--auth_client_id\", help=\"Auth0 client ID\"\n),\nauth_audience: str = typer.Option(\nconfig.auth_audience,\n\"--auth_audience\",\nhelp=\"Server's Auth0 API identifier\",\n),\ncertificate: str = typer.Option(\nconfig.certificate, \"--certificate\", help=\"path to a valid SSL certificate\"\n),\nloglevel: str = typer.Option(\nconfig.loglevel,\n\"--loglevel\",\nhelp=\"Logging level [debug | info | warning | error]\",\n),\nprepare_timeout: int = typer.Option(\nconfig.prepare_timeout,\n\"--prepare_timeout\",\nhelp=\"Maximum time in seconds before interrupting prepare task\",\n),\nsanity_check_timeout: int = typer.Option(\nconfig.sanity_check_timeout,\n\"--sanity_check_timeout\",\nhelp=\"Maximum time in seconds before interrupting sanity_check task\",\n),\nstatistics_timeout: int = typer.Option(\nconfig.statistics_timeout,\n\"--statistics_timeout\",\nhelp=\"Maximum time in seconds before interrupting statistics task\",\n),\ninfer_timeout: int = typer.Option(\nconfig.infer_timeout,\n\"--infer_timeout\",\nhelp=\"Maximum time in seconds before interrupting infer task\",\n),\nevaluate_timeout: int = typer.Option(\nconfig.evaluate_timeout,\n\"--evaluate_timeout\",\nhelp=\"Maximum time in seconds before interrupting evaluate task\",\n),\ncontainer_loglevel: str = typer.Option(\nconfig.container_loglevel,\n\"--container-loglevel\",\nhelp=\"Logging level for containers to be run [debug | info | warning | error]\",\n),\nplatform: str = typer.Option(\nconfig.platform,\n\"--platform\",\nhelp=\"Platform to use for MLCube. [docker | singularity]\",\n),\ngpus: str = typer.Option(\nconfig.gpus,\n\"--gpus\",\nhelp=\"\"\"\n            What GPUs to expose to MLCube.\n            Accepted Values are comma separated GPU IDs (e.g \"1,2\"), or \\\"all\\\".\n            MLCubes that aren't configured to use GPUs won't be affected by this.\n            Defaults to all available GPUs\"\"\",\n),\ncleanup: bool = typer.Option(\nconfig.cleanup,\n\"--cleanup/--no-cleanup\",\nhelp=\"Wether to clean up temporary medperf storage after execution\",\n),\n**kwargs,\n):\nreturn func(*args, **kwargs)\nreturn wrapper\n
"},{"location":"reference/enums/","title":"Enums","text":""},{"location":"reference/exceptions/","title":"Exceptions","text":""},{"location":"reference/exceptions/#exceptions.CleanExit","title":"CleanExit","text":"

Bases: MedperfException

Raised when Medperf needs to stop for non erroneous reasons

Source code in cli/medperf/exceptions.py
class CleanExit(MedperfException):\n\"\"\"Raised when Medperf needs to stop for non erroneous reasons\"\"\"\ndef __init__(self, *args, medperf_status_code=0) -> None:\nsuper().__init__(*args)\nself.medperf_status_code = medperf_status_code\n
"},{"location":"reference/exceptions/#exceptions.CommunicationAuthenticationError","title":"CommunicationAuthenticationError","text":"

Bases: CommunicationError

Raised when the communication interface can't handle an authentication request

Source code in cli/medperf/exceptions.py
class CommunicationAuthenticationError(CommunicationError):\n\"\"\"Raised when the communication interface can't handle an authentication request\"\"\"\n
"},{"location":"reference/exceptions/#exceptions.CommunicationError","title":"CommunicationError","text":"

Bases: MedperfException

Raised when an error happens due to the communication interface

Source code in cli/medperf/exceptions.py
class CommunicationError(MedperfException):\n\"\"\"Raised when an error happens due to the communication interface\"\"\"\n
"},{"location":"reference/exceptions/#exceptions.CommunicationRequestError","title":"CommunicationRequestError","text":"

Bases: CommunicationError

Raised when the communication interface can't handle a request appropiately

Source code in cli/medperf/exceptions.py
class CommunicationRequestError(CommunicationError):\n\"\"\"Raised when the communication interface can't handle a request appropiately\"\"\"\n
"},{"location":"reference/exceptions/#exceptions.CommunicationRetrievalError","title":"CommunicationRetrievalError","text":"

Bases: CommunicationError

Raised when the communication interface can't retrieve an element

Source code in cli/medperf/exceptions.py
class CommunicationRetrievalError(CommunicationError):\n\"\"\"Raised when the communication interface can't retrieve an element\"\"\"\n
"},{"location":"reference/exceptions/#exceptions.ExecutionError","title":"ExecutionError","text":"

Bases: MedperfException

Raised when an execution component fails

Source code in cli/medperf/exceptions.py
class ExecutionError(MedperfException):\n\"\"\"Raised when an execution component fails\"\"\"\n
"},{"location":"reference/exceptions/#exceptions.InvalidArgumentError","title":"InvalidArgumentError","text":"

Bases: MedperfException

Raised when an argument or set of arguments are consided invalid

Source code in cli/medperf/exceptions.py
class InvalidArgumentError(MedperfException):\n\"\"\"Raised when an argument or set of arguments are consided invalid\"\"\"\n
"},{"location":"reference/exceptions/#exceptions.InvalidEntityError","title":"InvalidEntityError","text":"

Bases: MedperfException

Raised when an entity is considered invalid

Source code in cli/medperf/exceptions.py
class InvalidEntityError(MedperfException):\n\"\"\"Raised when an entity is considered invalid\"\"\"\n
"},{"location":"reference/exceptions/#exceptions.MedperfException","title":"MedperfException","text":"

Bases: Exception

Medperf base exception

Source code in cli/medperf/exceptions.py
class MedperfException(Exception):\n\"\"\"Medperf base exception\"\"\"\n
"},{"location":"reference/init/","title":"Init","text":""},{"location":"reference/utils/","title":"Utils","text":""},{"location":"reference/utils/#utils.approval_prompt","title":"approval_prompt(msg)","text":"

Helper function for prompting the user for things they have to explicitly approve.

Parameters:

Name Type Description Default msg str

What message to ask the user for approval.

required

Returns:

Name Type Description bool bool

Wether the user explicitly approved or not.

Source code in cli/medperf/utils.py
def approval_prompt(msg: str) -> bool:\n\"\"\"Helper function for prompting the user for things they have to explicitly approve.\n    Args:\n        msg (str): What message to ask the user for approval.\n    Returns:\n        bool: Wether the user explicitly approved or not.\n    \"\"\"\nlogging.info(\"Prompting for user's approval\")\nui = config.ui\napproval = None\nwhile approval is None or approval not in \"yn\":\napproval = ui.prompt(msg.strip() + \" \").lower()\nlogging.info(f\"User answered approval with {approval}\")\nreturn approval == \"y\"\n
"},{"location":"reference/utils/#utils.check_for_updates","title":"check_for_updates()","text":"

Check if the current branch is up-to-date with its remote counterpart using GitPython.

Source code in cli/medperf/utils.py
def check_for_updates() -> None:\n\"\"\"Check if the current branch is up-to-date with its remote counterpart using GitPython.\"\"\"\nrepo = Repo(config.BASE_DIR)\nif repo.bare:\nlogging.debug(\"Repo is bare\")\nreturn\nlogging.debug(f\"Current git commit: {repo.head.commit.hexsha}\")\ntry:\nfor remote in repo.remotes:\nremote.fetch()\nif repo.head.is_detached:\nlogging.debug(\"Repo is in detached state\")\nreturn\ncurrent_branch = repo.active_branch\ntracking_branch = current_branch.tracking_branch()\nif tracking_branch is None:\nlogging.debug(\"Current branch does not track a remote branch.\")\nreturn\nif current_branch.commit.hexsha == tracking_branch.commit.hexsha:\nlogging.debug(\"No git branch updates.\")\nreturn\nlogging.debug(\nf\"Git branch updates found: {current_branch.commit.hexsha} -> {tracking_branch.commit.hexsha}\"\n)\nconfig.ui.print_warning(\n\"MedPerf client updates found. Please, update your MedPerf installation.\"\n)\nexcept GitCommandError as e:\nlogging.debug(\n\"Exception raised during updates check. Maybe user checked out repo with git@ and private key\"\n\" or repo is in detached / non-tracked state?\"\n)\nlogging.debug(e)\n
"},{"location":"reference/utils/#utils.cleanup","title":"cleanup()","text":"

Removes clutter and unused files from the medperf folder structure.

Source code in cli/medperf/utils.py
def cleanup():\n\"\"\"Removes clutter and unused files from the medperf folder structure.\"\"\"\nif not config.cleanup:\nlogging.info(\"Cleanup disabled\")\nreturn\nfor path in config.tmp_paths:\nremove_path(path)\ntrash_folder = config.trash_folder\nif os.path.exists(trash_folder) and os.listdir(trash_folder):\nmsg = \"WARNING: Failed to premanently cleanup some files. Consider deleting\"\nmsg += f\" '{trash_folder}' manually to avoid unnecessary storage.\"\nconfig.ui.print_warning(msg)\n
"},{"location":"reference/utils/#utils.combine_proc_sp_text","title":"combine_proc_sp_text(proc)","text":"

Combines the output of a process and the spinner. Joins any string captured from the process with the spinner current text. Any strings ending with any other character from the subprocess will be returned later.

Parameters:

Name Type Description Default proc spawn

a pexpect spawned child

required

Returns:

Name Type Description str str

all non-carriage-return-ending string captured from proc

Source code in cli/medperf/utils.py
def combine_proc_sp_text(proc: spawn) -> str:\n\"\"\"Combines the output of a process and the spinner.\n    Joins any string captured from the process with the\n    spinner current text. Any strings ending with any other\n    character from the subprocess will be returned later.\n    Args:\n        proc (spawn): a pexpect spawned child\n    Returns:\n        str: all non-carriage-return-ending string captured from proc\n    \"\"\"\nui = config.ui\nproc_out = \"\"\nbreak_ = False\nlog_filter = _MLCubeOutputFilter(proc.pid)\nwhile not break_:\nif not proc.isalive():\nbreak_ = True\ntry:\nline = proc.readline()\nexcept TIMEOUT:\nlogging.error(\"Process timed out\")\nlogging.debug(proc_out)\nraise ExecutionError(\"Process timed out\")\nline = line.decode(\"utf-8\", \"ignore\")\nif not line:\ncontinue\n# Always log each line just in case the final proc_out\n# wasn't logged for some reason\nlogging.debug(line)\nproc_out += line\nif not log_filter.check_line(line):\nui.print(f\"{Fore.WHITE}{Style.DIM}{line.strip()}{Style.RESET_ALL}\")\nlogging.debug(\"MLCube process finished\")\nlogging.debug(proc_out)\nreturn proc_out\n
"},{"location":"reference/utils/#utils.dict_pretty_print","title":"dict_pretty_print(in_dict, skip_none_values=True)","text":"

Helper function for distinctively printing dictionaries with yaml format.

Parameters:

Name Type Description Default in_dict dict

dictionary to print

required skip_none_values bool

if fields with None values should be omitted

True Source code in cli/medperf/utils.py
def dict_pretty_print(in_dict: dict, skip_none_values: bool = True):\n\"\"\"Helper function for distinctively printing dictionaries with yaml format.\n    Args:\n        in_dict (dict): dictionary to print\n        skip_none_values (bool): if fields with `None` values should be omitted\n    \"\"\"\nlogging.debug(f\"Printing dictionary to the user: {in_dict}\")\nui = config.ui\nui.print()\nui.print(\"=\" * 20)\nif skip_none_values:\nin_dict = {k: v for (k, v) in in_dict.items() if v is not None}\nui.print(yaml.dump(in_dict))\nlogging.debug(f\"Dictionary printed to the user: {in_dict}\")\nui.print(\"=\" * 20)\n
"},{"location":"reference/utils/#utils.filter_latest_associations","title":"filter_latest_associations(associations, entity_key)","text":"

Given a list of entity-benchmark associations, this function retrieves a list containing the latest association of each entity instance.

Parameters:

Name Type Description Default associations list[dict]

the list of associations

required entity_key str

either \"dataset\" or \"model_mlcube\"

required

Returns:

Type Description

list[dict]: the list containing the latest association of each entity instance.

Source code in cli/medperf/utils.py
def filter_latest_associations(associations, entity_key):\n\"\"\"Given a list of entity-benchmark associations, this function\n    retrieves a list containing the latest association of each\n    entity instance.\n    Args:\n        associations (list[dict]): the list of associations\n        entity_key (str): either \"dataset\" or \"model_mlcube\"\n    Returns:\n        list[dict]: the list containing the latest association of each\n                    entity instance.\n    \"\"\"\nassociations.sort(key=lambda assoc: parse_datetime(assoc[\"created_at\"]))\nlatest_associations = {}\nfor assoc in associations:\nentity_id = assoc[entity_key]\nlatest_associations[entity_id] = assoc\nlatest_associations = list(latest_associations.values())\nreturn latest_associations\n
"},{"location":"reference/utils/#utils.format_errors_dict","title":"format_errors_dict(errors_dict)","text":"

Reformats the error details from a field-error(s) dictionary into a human-readable string for printing

Source code in cli/medperf/utils.py
def format_errors_dict(errors_dict: dict):\n\"\"\"Reformats the error details from a field-error(s) dictionary into a human-readable string for printing\"\"\"\nerror_msg = \"\"\nfor field, errors in errors_dict.items():\nerror_msg += \"\\n\"\nif isinstance(field, tuple):\nfield = field[0]\nerror_msg += f\"- {field}: \"\nif isinstance(errors, str):\nerror_msg += errors\nelif len(errors) == 1:\n# If a single error for a field is given, don't create a sublist\nerror_msg += errors[0]\nelse:\n# Create a sublist otherwise\nfor e_msg in errors:\nerror_msg += \"\\n\"\nerror_msg += f\"\\t- {e_msg}\"\nreturn error_msg\n
"},{"location":"reference/utils/#utils.generate_tmp_path","title":"generate_tmp_path()","text":"

Generates a temporary path by means of getting the current timestamp with a random salt

Returns:

Name Type Description str str

generated temporary path

Source code in cli/medperf/utils.py
def generate_tmp_path() -> str:\n\"\"\"Generates a temporary path by means of getting the current timestamp\n    with a random salt\n    Returns:\n        str: generated temporary path\n    \"\"\"\ntmp_path = os.path.join(config.tmp_folder, generate_tmp_uid())\nconfig.tmp_paths.append(tmp_path)\nreturn tmp_path\n
"},{"location":"reference/utils/#utils.generate_tmp_uid","title":"generate_tmp_uid()","text":"

Generates a temporary uid by means of getting the current timestamp with a random salt

Returns:

Name Type Description str str

generated temporary uid

Source code in cli/medperf/utils.py
def generate_tmp_uid() -> str:\n\"\"\"Generates a temporary uid by means of getting the current timestamp\n    with a random salt\n    Returns:\n        str: generated temporary uid\n    \"\"\"\ndt = datetime.utcnow()\nts_int = int(datetime.timestamp(dt))\nsalt = random.randint(-ts_int, ts_int)\nts = str(ts_int + salt)\nreturn ts\n
"},{"location":"reference/utils/#utils.get_cube_image_name","title":"get_cube_image_name(cube_path)","text":"

Retrieves the singularity image name of the mlcube by reading its mlcube.yaml file

Source code in cli/medperf/utils.py
def get_cube_image_name(cube_path: str) -> str:\n\"\"\"Retrieves the singularity image name of the mlcube by reading its mlcube.yaml file\"\"\"\ncube_config_path = os.path.join(cube_path, config.cube_filename)\nwith open(cube_config_path, \"r\") as f:\ncube_config = yaml.safe_load(f)\ntry:\n# TODO: Why do we check singularity only there? Why not docker?\nreturn cube_config[\"singularity\"][\"image\"]\nexcept KeyError:\nmsg = \"The provided mlcube doesn't seem to be configured for singularity\"\nraise MedperfException(msg)\n
"},{"location":"reference/utils/#utils.get_file_hash","title":"get_file_hash(path)","text":"

Calculates the sha256 hash for a given file.

Parameters:

Name Type Description Default path str

Location of the file of interest.

required

Returns:

Name Type Description str str

Calculated hash

Source code in cli/medperf/utils.py
def get_file_hash(path: str) -> str:\n\"\"\"Calculates the sha256 hash for a given file.\n    Args:\n        path (str): Location of the file of interest.\n    Returns:\n        str: Calculated hash\n    \"\"\"\nlogging.debug(\"Calculating hash for file {}\".format(path))\nBUF_SIZE = 65536\nsha = hashlib.sha256()\nwith open(path, \"rb\") as f:\nwhile True:\ndata = f.read(BUF_SIZE)\nif not data:\nbreak\nsha.update(data)\nsha_val = sha.hexdigest()\nlogging.debug(f\"Hash for file {path}: {sha_val}\")\nreturn sha_val\n
"},{"location":"reference/utils/#utils.get_folders_hash","title":"get_folders_hash(paths)","text":"

Generates a hash for all the contents of the fiven folders. This procedure hashes all the files in all passed folders, sorts them and then hashes that list.

Parameters:

Name Type Description Default paths List(str

Folders to hash.

required

Returns:

Name Type Description str str

sha256 hash that represents all the folders altogether

Source code in cli/medperf/utils.py
def get_folders_hash(paths: List[str]) -> str:\n\"\"\"Generates a hash for all the contents of the fiven folders. This procedure\n    hashes all the files in all passed folders, sorts them and then hashes that list.\n    Args:\n        paths List(str): Folders to hash.\n    Returns:\n        str: sha256 hash that represents all the folders altogether\n    \"\"\"\nhashes = []\n# The hash doesn't depend on the order of paths or folders, as the hashes get sorted after the fact\nfor path in paths:\nfor root, _, files in os.walk(path, topdown=False):\nfor file in files:\nlogging.debug(f\"Hashing file {file}\")\nfilepath = os.path.join(root, file)\nhashes.append(get_file_hash(filepath))\nhashes = sorted(hashes)\nsha = hashlib.sha256()\nfor hash in hashes:\nsha.update(hash.encode(\"utf-8\"))\nhash_val = sha.hexdigest()\nlogging.debug(f\"Folder hash: {hash_val}\")\nreturn hash_val\n
"},{"location":"reference/utils/#utils.get_uids","title":"get_uids(path)","text":"

Retrieves the UID of all the elements in the specified path.

Returns:

Type Description List[str]

List[str]: UIDs of objects in path.

Source code in cli/medperf/utils.py
def get_uids(path: str) -> List[str]:\n\"\"\"Retrieves the UID of all the elements in the specified path.\n    Returns:\n        List[str]: UIDs of objects in path.\n    \"\"\"\nlogging.debug(\"Retrieving datasets\")\nuids = next(os.walk(path))[1]\nlogging.debug(f\"Found {len(uids)} datasets\")\nlogging.debug(f\"Datasets: {uids}\")\nreturn uids\n
"},{"location":"reference/utils/#utils.pretty_error","title":"pretty_error(msg)","text":"

Prints an error message with typer protocol

Parameters:

Name Type Description Default msg str

Error message to show to the user

required Source code in cli/medperf/utils.py
def pretty_error(msg: str):\n\"\"\"Prints an error message with typer protocol\n    Args:\n        msg (str): Error message to show to the user\n    \"\"\"\nui = config.ui\nlogging.warning(\n\"MedPerf had to stop execution. See logs above for more information\"\n)\nif msg[-1] != \".\":\nmsg = msg + \".\"\nui.print_error(msg)\n
"},{"location":"reference/utils/#utils.remove_path","title":"remove_path(path)","text":"

Cleans up a clutter object. In case of failure, it is moved to .trash

Source code in cli/medperf/utils.py
def remove_path(path):\n\"\"\"Cleans up a clutter object. In case of failure, it is moved to `.trash`\"\"\"\n# NOTE: We assume medperf will always have permissions to unlink\n# and rename clutter paths, since for now they are expected to live\n# in folders owned by medperf\nif not os.path.exists(path):\nreturn\nlogging.info(f\"Removing clutter path: {path}\")\n# Don't delete symlinks\nif os.path.islink(path):\nos.unlink(path)\nreturn\ntry:\nif os.path.isfile(path):\nos.remove(path)\nelse:\nshutil.rmtree(path)\nexcept OSError as e:\nlogging.error(f\"Could not remove {path}: {str(e)}\")\nmove_to_trash(path)\n
"},{"location":"reference/utils/#utils.sanitize_json","title":"sanitize_json(data)","text":"

Makes sure the input data is JSON compliant.

Parameters:

Name Type Description Default data dict

dictionary containing data to be represented as JSON.

required

Returns:

Name Type Description dict dict

sanitized dictionary

Source code in cli/medperf/utils.py
def sanitize_json(data: dict) -> dict:\n\"\"\"Makes sure the input data is JSON compliant.\n    Args:\n        data (dict): dictionary containing data to be represented as JSON.\n    Returns:\n        dict: sanitized dictionary\n    \"\"\"\njson_string = json.dumps(data)\njson_string = re.sub(r\"\\bNaN\\b\", '\"nan\"', json_string)\njson_string = re.sub(r\"(-?)\\bInfinity\\b\", r'\"\\1Infinity\"', json_string)\ndata = json.loads(json_string)\nreturn data\n
"},{"location":"reference/utils/#utils.untar","title":"untar(filepath, remove=True)","text":"

Untars and optionally removes the tar.gz file

Parameters:

Name Type Description Default filepath str

Path where the tar.gz file can be found.

required remove bool

Wether to delete the tar.gz file. Defaults to True.

True

Returns:

Name Type Description str str

location where the untared files can be found.

Source code in cli/medperf/utils.py
def untar(filepath: str, remove: bool = True) -> str:\n\"\"\"Untars and optionally removes the tar.gz file\n    Args:\n        filepath (str): Path where the tar.gz file can be found.\n        remove (bool): Wether to delete the tar.gz file. Defaults to True.\n    Returns:\n        str: location where the untared files can be found.\n    \"\"\"\nlogging.info(f\"Uncompressing tar.gz at {filepath}\")\naddpath = str(Path(filepath).parent)\ntar = tarfile.open(filepath)\ntar.extractall(addpath)\ntar.close()\n# OS Specific issue: Mac Creates superfluous files with tarfile library\n[\nremove_path(spurious_file)\nfor spurious_file in glob(addpath + \"/**/._*\", recursive=True)\n]\nif remove:\nlogging.info(f\"Deleting {filepath}\")\nremove_path(filepath)\nreturn addpath\n
"},{"location":"reference/account_management/account_management/","title":"Account management","text":""},{"location":"reference/account_management/account_management/#account_management.account_management.get_medperf_user_data","title":"get_medperf_user_data()","text":"

Return cached medperf user data. Get from the server if not found

Source code in cli/medperf/account_management/account_management.py
def get_medperf_user_data():\n\"\"\"Return cached medperf user data. Get from the server if not found\"\"\"\nconfig_p = read_config()\nif config.credentials_keyword not in config_p.active_profile:\nraise MedperfException(\"You are not logged in\")\nmedperf_user = config_p.active_profile[config.credentials_keyword].get(\n\"medperf_user\", None\n)\nif medperf_user is None:\nmedperf_user = set_medperf_user_data()\nreturn medperf_user\n
"},{"location":"reference/account_management/account_management/#account_management.account_management.set_medperf_user_data","title":"set_medperf_user_data()","text":"

Get and cache user data from the MedPerf server

Source code in cli/medperf/account_management/account_management.py
def set_medperf_user_data():\n\"\"\"Get and cache user data from the MedPerf server\"\"\"\nconfig_p = read_config()\nmedperf_user = config.comms.get_current_user()\nconfig_p.active_profile[config.credentials_keyword][\"medperf_user\"] = medperf_user\nwrite_config(config_p)\nreturn medperf_user\n
"},{"location":"reference/account_management/token_storage/filesystem/","title":"Filesystem","text":""},{"location":"reference/account_management/token_storage/keyring_/","title":"Keyring","text":"

Keyring token storage is NOT used. We used it before this commit but users who connect to remote machines through passwordless SSH faced some issues.

"},{"location":"reference/commands/execution/","title":"Execution","text":""},{"location":"reference/commands/execution/#commands.execution.Execution","title":"Execution","text":"Source code in cli/medperf/commands/execution.py
class Execution:\n@classmethod\ndef run(\ncls, dataset: Dataset, model: Cube, evaluator: Cube, ignore_model_errors=False\n):\n\"\"\"Benchmark execution flow.\n        Args:\n            benchmark_uid (int): UID of the desired benchmark\n            data_uid (str): Registered Dataset UID\n            model_uid (int): UID of model to execute\n        \"\"\"\nexecution = cls(dataset, model, evaluator, ignore_model_errors)\nexecution.prepare()\nwith execution.ui.interactive():\nexecution.run_inference()\nexecution.run_evaluation()\nexecution_summary = execution.todict()\nreturn execution_summary\ndef __init__(\nself, dataset: Dataset, model: Cube, evaluator: Cube, ignore_model_errors=False\n):\nself.comms = config.comms\nself.ui = config.ui\nself.dataset = dataset\nself.model = model\nself.evaluator = evaluator\nself.ignore_model_errors = ignore_model_errors\ndef prepare(self):\nself.partial = False\nself.preds_path = self.__setup_predictions_path()\nself.model_logs_path, self.metrics_logs_path = self.__setup_logs_path()\nself.results_path = generate_tmp_path()\nlogging.debug(f\"tmp results output: {self.results_path}\")\ndef __setup_logs_path(self):\nmodel_uid = self.model.generated_uid\neval_uid = self.evaluator.generated_uid\ndata_hash = self.dataset.generated_uid\nlogs_path = os.path.join(\nconfig.experiments_logs_folder, str(model_uid), str(data_hash)\n)\nos.makedirs(logs_path, exist_ok=True)\nmodel_logs_path = os.path.join(logs_path, \"model.log\")\nmetrics_logs_path = os.path.join(logs_path, f\"metrics_{eval_uid}.log\")\nreturn model_logs_path, metrics_logs_path\ndef __setup_predictions_path(self):\nmodel_uid = self.model.generated_uid\ndata_hash = self.dataset.generated_uid\npreds_path = os.path.join(\nconfig.predictions_folder, str(model_uid), str(data_hash)\n)\nif os.path.exists(preds_path):\nmsg = f\"Found existing predictions for model {self.model.id} on dataset \"\nmsg += f\"{self.dataset.id} at {preds_path}. Consider deleting this \"\nmsg += \"folder if you wish to overwrite the predictions.\"\nraise ExecutionError(msg)\nreturn preds_path\ndef run_inference(self):\nself.ui.text = \"Running model inference on dataset\"\ninfer_timeout = config.infer_timeout\npreds_path = self.preds_path\ndata_path = self.dataset.data_path\ntry:\nself.model.run(\ntask=\"infer\",\noutput_logs=self.model_logs_path,\ntimeout=infer_timeout,\ndata_path=data_path,\noutput_path=preds_path,\n)\nself.ui.print(\"> Model execution complete\")\nexcept ExecutionError as e:\nif not self.ignore_model_errors:\nlogging.error(f\"Model MLCube Execution failed: {e}\")\nraise ExecutionError(f\"Model MLCube failed: {e}\")\nelse:\nself.partial = True\nlogging.warning(f\"Model MLCube Execution failed: {e}\")\ndef run_evaluation(self):\nself.ui.text = \"Running model evaluation on dataset\"\nevaluate_timeout = config.evaluate_timeout\npreds_path = self.preds_path\nlabels_path = self.dataset.labels_path\nresults_path = self.results_path\nself.ui.text = \"Evaluating results\"\ntry:\nself.evaluator.run(\ntask=\"evaluate\",\noutput_logs=self.metrics_logs_path,\ntimeout=evaluate_timeout,\npredictions=preds_path,\nlabels=labels_path,\noutput_path=results_path,\n)\nexcept ExecutionError as e:\nlogging.error(f\"Metrics MLCube Execution failed: {e}\")\nraise ExecutionError(\"Metrics MLCube failed\")\ndef todict(self):\nreturn {\n\"results\": self.get_results(),\n\"partial\": self.partial,\n}\ndef get_results(self):\nwith open(self.results_path, \"r\") as f:\nresults = yaml.safe_load(f)\nreturn results\n
"},{"location":"reference/commands/execution/#commands.execution.Execution.run","title":"run(dataset, model, evaluator, ignore_model_errors=False) classmethod","text":"

Benchmark execution flow.

Parameters:

Name Type Description Default benchmark_uid int

UID of the desired benchmark

required data_uid str

Registered Dataset UID

required model_uid int

UID of model to execute

required Source code in cli/medperf/commands/execution.py
@classmethod\ndef run(\ncls, dataset: Dataset, model: Cube, evaluator: Cube, ignore_model_errors=False\n):\n\"\"\"Benchmark execution flow.\n    Args:\n        benchmark_uid (int): UID of the desired benchmark\n        data_uid (str): Registered Dataset UID\n        model_uid (int): UID of model to execute\n    \"\"\"\nexecution = cls(dataset, model, evaluator, ignore_model_errors)\nexecution.prepare()\nwith execution.ui.interactive():\nexecution.run_inference()\nexecution.run_evaluation()\nexecution_summary = execution.todict()\nreturn execution_summary\n
"},{"location":"reference/commands/list/","title":"List","text":""},{"location":"reference/commands/list/#commands.list.EntityList","title":"EntityList","text":"Source code in cli/medperf/commands/list.py
class EntityList:\n@staticmethod\ndef run(\nentity_class,\nfields,\nlocal_only: bool = False,\nmine_only: bool = False,\n**kwargs,\n):\n\"\"\"Lists all local datasets\n        Args:\n            local_only (bool, optional): Display all local results. Defaults to False.\n            mine_only (bool, optional): Display all current-user results. Defaults to False.\n            kwargs (dict): Additional parameters for filtering entity lists.\n        \"\"\"\nentity_list = EntityList(entity_class, fields, local_only, mine_only, **kwargs)\nentity_list.prepare()\nentity_list.validate()\nentity_list.filter()\nentity_list.display()\ndef __init__(self, entity_class, fields, local_only, mine_only, **kwargs):\nself.entity_class = entity_class\nself.fields = fields\nself.local_only = local_only\nself.mine_only = mine_only\nself.filters = kwargs\nself.data = []\ndef prepare(self):\nif self.mine_only:\nself.filters[\"owner\"] = get_medperf_user_data()[\"id\"]\nentities = self.entity_class.all(\nlocal_only=self.local_only, filters=self.filters\n)\nself.data = [entity.display_dict() for entity in entities]\ndef validate(self):\nif self.data:\nvalid_fields = set(self.data[0].keys())\nchosen_fields = set(self.fields)\nif not chosen_fields.issubset(valid_fields):\ninvalid_fields = chosen_fields.difference(valid_fields)\ninvalid_fields = \", \".join(invalid_fields)\nraise InvalidArgumentError(f\"Invalid field(s): {invalid_fields}\")\ndef filter(self):\nself.data = [\n{field: entity_dict[field] for field in self.fields}\nfor entity_dict in self.data\n]\ndef display(self):\nheaders = self.fields\ndata_lists = [list(entity_dict.values()) for entity_dict in self.data]\ntab = tabulate(data_lists, headers=headers)\nconfig.ui.print(tab)\n
"},{"location":"reference/commands/list/#commands.list.EntityList.run","title":"run(entity_class, fields, local_only=False, mine_only=False, **kwargs) staticmethod","text":"

Lists all local datasets

Parameters:

Name Type Description Default local_only bool

Display all local results. Defaults to False.

False mine_only bool

Display all current-user results. Defaults to False.

False kwargs dict

Additional parameters for filtering entity lists.

{} Source code in cli/medperf/commands/list.py
@staticmethod\ndef run(\nentity_class,\nfields,\nlocal_only: bool = False,\nmine_only: bool = False,\n**kwargs,\n):\n\"\"\"Lists all local datasets\n    Args:\n        local_only (bool, optional): Display all local results. Defaults to False.\n        mine_only (bool, optional): Display all current-user results. Defaults to False.\n        kwargs (dict): Additional parameters for filtering entity lists.\n    \"\"\"\nentity_list = EntityList(entity_class, fields, local_only, mine_only, **kwargs)\nentity_list.prepare()\nentity_list.validate()\nentity_list.filter()\nentity_list.display()\n
"},{"location":"reference/commands/profile/","title":"Profile","text":""},{"location":"reference/commands/profile/#commands.profile.activate","title":"activate(profile)","text":"

Assigns the active profile, which is used by default

Parameters:

Name Type Description Default profile str

Name of the profile to be used.

required Source code in cli/medperf/commands/profile.py
@app.command(\"activate\")\n@clean_except\ndef activate(profile: str):\n\"\"\"Assigns the active profile, which is used by default\n    Args:\n        profile (str): Name of the profile to be used.\n    \"\"\"\nconfig_p = read_config()\nif profile not in config_p:\nraise InvalidArgumentError(\"The provided profile does not exists\")\nconfig_p.activate(profile)\nwrite_config(config_p)\n
"},{"location":"reference/commands/profile/#commands.profile.create","title":"create(ctx, name=typer.Option(..., '--name', '-n', help=\"Profile's name\"))","text":"

Creates a new profile for managing and customizing configuration

Source code in cli/medperf/commands/profile.py
@app.command(\"create\")\n@clean_except\n@configurable\ndef create(\nctx: typer.Context,\nname: str = typer.Option(..., \"--name\", \"-n\", help=\"Profile's name\"),\n):\n\"\"\"Creates a new profile for managing and customizing configuration\"\"\"\nargs = ctx.params\nargs.pop(\"name\")\nconfig_p = read_config()\nif name in config_p:\nraise InvalidArgumentError(\"A profile with the same name already exists\")\nconfig_p[name] = args\nwrite_config(config_p)\n
"},{"location":"reference/commands/profile/#commands.profile.delete","title":"delete(profile)","text":"

Deletes a profile's configuration.

Parameters:

Name Type Description Default profile str

Profile to delete.

required Source code in cli/medperf/commands/profile.py
@app.command(\"delete\")\n@clean_except\ndef delete(profile: str):\n\"\"\"Deletes a profile's configuration.\n    Args:\n        profile (str): Profile to delete.\n    \"\"\"\nconfig_p = read_config()\nif profile not in config_p.profiles:\nraise InvalidArgumentError(\"The provided profile does not exists\")\nif profile in [\nconfig.default_profile_name,\nconfig.testauth_profile_name,\nconfig.test_profile_name,\n]:\nraise InvalidArgumentError(\"Cannot delete reserved profiles\")\nif config_p.is_profile_active(profile):\nraise InvalidArgumentError(\"Cannot delete a currently activated profile\")\ndel config_p[profile]\nwrite_config(config_p)\n
"},{"location":"reference/commands/profile/#commands.profile.list","title":"list()","text":"

Lists all available profiles

Source code in cli/medperf/commands/profile.py
@app.command(\"ls\")\n@clean_except\ndef list():\n\"\"\"Lists all available profiles\"\"\"\nui = config.ui\nconfig_p = read_config()\nfor profile in config_p:\nif config_p.is_profile_active(profile):\nui.print_highlight(\"* \" + profile)\nelse:\nui.print(\"  \" + profile)\n
"},{"location":"reference/commands/profile/#commands.profile.set_args","title":"set_args(ctx)","text":"

Assign key-value configuration pairs to the current profile.

Source code in cli/medperf/commands/profile.py
@app.command(\"set\")\n@clean_except\n@configurable\ndef set_args(ctx: typer.Context):\n\"\"\"Assign key-value configuration pairs to the current profile.\"\"\"\nargs = ctx.params\nconfig_p = read_config()\nconfig_p.active_profile.update(args)\nwrite_config(config_p)\n
"},{"location":"reference/commands/profile/#commands.profile.view","title":"view(profile=typer.Argument(None))","text":"

Displays a profile's configuration.

Parameters:

Name Type Description Default profile str

Profile to display information from. Defaults to active profile.

typer.Argument(None) Source code in cli/medperf/commands/profile.py
@app.command(\"view\")\n@clean_except\ndef view(profile: str = typer.Argument(None)):\n\"\"\"Displays a profile's configuration.\n    Args:\n        profile (str, optional): Profile to display information from. Defaults to active profile.\n    \"\"\"\nconfig_p = read_config()\nprofile_config = config_p.active_profile\nif profile:\nprofile_config = config_p[profile]\nprofile_config.pop(config.credentials_keyword, None)\nprofile_name = profile if profile else config_p.active_profile_name\nconfig.ui.print(f\"\\nProfile '{profile_name}':\")\ndict_pretty_print(profile_config, skip_none_values=False)\n
"},{"location":"reference/commands/storage/","title":"Storage","text":""},{"location":"reference/commands/storage/#commands.storage.clean","title":"clean()","text":"

Cleans up clutter paths

Source code in cli/medperf/commands/storage.py
@app.command(\"cleanup\")\ndef clean():\n\"\"\"Cleans up clutter paths\"\"\"\n# Force cleanup to be true\nconfig.cleanup = True\ncleanup()\n
"},{"location":"reference/commands/storage/#commands.storage.ls","title":"ls()","text":"

Show the location of the current medperf assets

Source code in cli/medperf/commands/storage.py
@app.command(\"ls\")\n@clean_except\ndef ls():\n\"\"\"Show the location of the current medperf assets\"\"\"\nheaders = [\"Asset\", \"Location\"]\ninfo = []\nfor folder in config.storage:\ninfo.append((folder, config.storage[folder][\"base\"]))\ntab = tabulate(info, headers=headers)\nconfig.ui.print(tab)\n
"},{"location":"reference/commands/storage/#commands.storage.move","title":"move(path=typer.Option(..., '--target', '-t', help='Target path'))","text":"

Moves all storage folders to a target base path. Folders include: Benchmarks, datasets, mlcubes, results, tests, ...

Parameters:

Name Type Description Default path str

target path

typer.Option(..., '--target', '-t', help='Target path') Source code in cli/medperf/commands/storage.py
@app.command(\"move\")\n@clean_except\ndef move(path: str = typer.Option(..., \"--target\", \"-t\", help=\"Target path\")):\n\"\"\"Moves all storage folders to a target base path. Folders include:\n    Benchmarks, datasets, mlcubes, results, tests, ...\n    Args:\n        path (str): target path\n    \"\"\"\nmove_storage(path)\n
"},{"location":"reference/commands/view/","title":"View","text":""},{"location":"reference/commands/view/#commands.view.EntityView","title":"EntityView","text":"Source code in cli/medperf/commands/view.py
class EntityView:\n@staticmethod\ndef run(\nentity_id: Union[int, str],\nentity_class: Entity,\nformat: str = \"yaml\",\nlocal_only: bool = False,\nmine_only: bool = False,\noutput: str = None,\n**kwargs,\n):\n\"\"\"Displays the contents of a single or multiple entities of a given type\n        Args:\n            entity_id (Union[int, str]): Entity identifies\n            entity_class (Entity): Entity type\n            local_only (bool, optional): Display all local entities. Defaults to False.\n            mine_only (bool, optional): Display all current-user entities. Defaults to False.\n            format (str, optional): What format to use to display the contents. Valid formats: [yaml, json]. Defaults to yaml.\n            output (str, optional): Path to a file for storing the entity contents. If not provided, the contents are printed.\n            kwargs (dict): Additional parameters for filtering entity lists.\n        \"\"\"\nentity_view = EntityView(\nentity_id, entity_class, format, local_only, mine_only, output, **kwargs\n)\nentity_view.validate()\nentity_view.prepare()\nif output is None:\nentity_view.display()\nelse:\nentity_view.store()\ndef __init__(\nself, entity_id, entity_class, format, local_only, mine_only, output, **kwargs\n):\nself.entity_id = entity_id\nself.entity_class = entity_class\nself.format = format\nself.local_only = local_only\nself.mine_only = mine_only\nself.output = output\nself.filters = kwargs\nself.data = []\ndef validate(self):\nvalid_formats = set([\"yaml\", \"json\"])\nif self.format not in valid_formats:\nraise InvalidArgumentError(\"The provided format is not supported\")\ndef prepare(self):\nif self.entity_id is not None:\nentities = [self.entity_class.get(self.entity_id)]\nelse:\nif self.mine_only:\nself.filters[\"owner\"] = get_medperf_user_data()[\"id\"]\nentities = self.entity_class.all(\nlocal_only=self.local_only, filters=self.filters\n)\nself.data = [entity.todict() for entity in entities]\nif self.entity_id is not None:\n# User expects a single entity if id provided\n# Don't output the view as a list of entities\nself.data = self.data[0]\ndef display(self):\nif self.format == \"json\":\nformatter = json.dumps\nif self.format == \"yaml\":\nformatter = yaml.dump\nformatted_data = formatter(self.data)\nconfig.ui.print(formatted_data)\ndef store(self):\nif self.format == \"json\":\nformatter = json.dump\nif self.format == \"yaml\":\nformatter = yaml.dump\nwith open(self.output, \"w\") as f:\nformatter(self.data, f)\n
"},{"location":"reference/commands/view/#commands.view.EntityView.run","title":"run(entity_id, entity_class, format='yaml', local_only=False, mine_only=False, output=None, **kwargs) staticmethod","text":"

Displays the contents of a single or multiple entities of a given type

Parameters:

Name Type Description Default entity_id Union[int, str]

Entity identifies

required entity_class Entity

Entity type

required local_only bool

Display all local entities. Defaults to False.

False mine_only bool

Display all current-user entities. Defaults to False.

False format str

What format to use to display the contents. Valid formats: [yaml, json]. Defaults to yaml.

'yaml' output str

Path to a file for storing the entity contents. If not provided, the contents are printed.

None kwargs dict

Additional parameters for filtering entity lists.

{} Source code in cli/medperf/commands/view.py
@staticmethod\ndef run(\nentity_id: Union[int, str],\nentity_class: Entity,\nformat: str = \"yaml\",\nlocal_only: bool = False,\nmine_only: bool = False,\noutput: str = None,\n**kwargs,\n):\n\"\"\"Displays the contents of a single or multiple entities of a given type\n    Args:\n        entity_id (Union[int, str]): Entity identifies\n        entity_class (Entity): Entity type\n        local_only (bool, optional): Display all local entities. Defaults to False.\n        mine_only (bool, optional): Display all current-user entities. Defaults to False.\n        format (str, optional): What format to use to display the contents. Valid formats: [yaml, json]. Defaults to yaml.\n        output (str, optional): Path to a file for storing the entity contents. If not provided, the contents are printed.\n        kwargs (dict): Additional parameters for filtering entity lists.\n    \"\"\"\nentity_view = EntityView(\nentity_id, entity_class, format, local_only, mine_only, output, **kwargs\n)\nentity_view.validate()\nentity_view.prepare()\nif output is None:\nentity_view.display()\nelse:\nentity_view.store()\n
"},{"location":"reference/commands/association/approval/","title":"Approval","text":""},{"location":"reference/commands/association/approval/#commands.association.approval.Approval","title":"Approval","text":"Source code in cli/medperf/commands/association/approval.py
class Approval:\n@staticmethod\ndef run(\nbenchmark_uid: int,\napproval_status: str,\ndataset_uid: int = None,\nmlcube_uid: int = None,\n):\n\"\"\"Sets approval status for an association between a benchmark and a dataset or mlcube\n        Args:\n            benchmark_uid (int): Benchmark UID.\n            approval_status (str): Desired approval status to set for the association.\n            comms (Comms): Instance of Comms interface.\n            ui (UI): Instance of UI interface.\n            dataset_uid (int, optional): Dataset UID. Defaults to None.\n            mlcube_uid (int, optional): MLCube UID. Defaults to None.\n        \"\"\"\ncomms = config.comms\ntoo_many_resources = dataset_uid and mlcube_uid\nno_resource = dataset_uid is None and mlcube_uid is None\nif no_resource or too_many_resources:\nraise InvalidArgumentError(\"Must provide either a dataset or mlcube\")\nif dataset_uid:\ncomms.set_dataset_association_approval(\nbenchmark_uid, dataset_uid, approval_status.value\n)\nif mlcube_uid:\ncomms.set_mlcube_association_approval(\nbenchmark_uid, mlcube_uid, approval_status.value\n)\n
"},{"location":"reference/commands/association/approval/#commands.association.approval.Approval.run","title":"run(benchmark_uid, approval_status, dataset_uid=None, mlcube_uid=None) staticmethod","text":"

Sets approval status for an association between a benchmark and a dataset or mlcube

Parameters:

Name Type Description Default benchmark_uid int

Benchmark UID.

required approval_status str

Desired approval status to set for the association.

required comms Comms

Instance of Comms interface.

required ui UI

Instance of UI interface.

required dataset_uid int

Dataset UID. Defaults to None.

None mlcube_uid int

MLCube UID. Defaults to None.

None Source code in cli/medperf/commands/association/approval.py
@staticmethod\ndef run(\nbenchmark_uid: int,\napproval_status: str,\ndataset_uid: int = None,\nmlcube_uid: int = None,\n):\n\"\"\"Sets approval status for an association between a benchmark and a dataset or mlcube\n    Args:\n        benchmark_uid (int): Benchmark UID.\n        approval_status (str): Desired approval status to set for the association.\n        comms (Comms): Instance of Comms interface.\n        ui (UI): Instance of UI interface.\n        dataset_uid (int, optional): Dataset UID. Defaults to None.\n        mlcube_uid (int, optional): MLCube UID. Defaults to None.\n    \"\"\"\ncomms = config.comms\ntoo_many_resources = dataset_uid and mlcube_uid\nno_resource = dataset_uid is None and mlcube_uid is None\nif no_resource or too_many_resources:\nraise InvalidArgumentError(\"Must provide either a dataset or mlcube\")\nif dataset_uid:\ncomms.set_dataset_association_approval(\nbenchmark_uid, dataset_uid, approval_status.value\n)\nif mlcube_uid:\ncomms.set_mlcube_association_approval(\nbenchmark_uid, mlcube_uid, approval_status.value\n)\n
"},{"location":"reference/commands/association/association/","title":"Association","text":""},{"location":"reference/commands/association/association/#commands.association.association.approve","title":"approve(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), dataset_uid=typer.Option(None, '--dataset', '-d', help='Dataset UID'), mlcube_uid=typer.Option(None, '--mlcube', '-m', help='MLCube UID'))","text":"

Approves an association between a benchmark and a dataset or model mlcube

Parameters:

Name Type Description Default benchmark_uid int

Benchmark UID.

typer.Option(..., '--benchmark', '-b', help='Benchmark UID') dataset_uid int

Dataset UID.

typer.Option(None, '--dataset', '-d', help='Dataset UID') mlcube_uid int

Model MLCube UID.

typer.Option(None, '--mlcube', '-m', help='MLCube UID') Source code in cli/medperf/commands/association/association.py
@app.command(\"approve\")\n@clean_except\ndef approve(\nbenchmark_uid: int = typer.Option(..., \"--benchmark\", \"-b\", help=\"Benchmark UID\"),\ndataset_uid: int = typer.Option(None, \"--dataset\", \"-d\", help=\"Dataset UID\"),\nmlcube_uid: int = typer.Option(None, \"--mlcube\", \"-m\", help=\"MLCube UID\"),\n):\n\"\"\"Approves an association between a benchmark and a dataset or model mlcube\n    Args:\n        benchmark_uid (int): Benchmark UID.\n        dataset_uid (int, optional): Dataset UID.\n        mlcube_uid (int, optional): Model MLCube UID.\n    \"\"\"\nApproval.run(benchmark_uid, Status.APPROVED, dataset_uid, mlcube_uid)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/association/association/#commands.association.association.list","title":"list(filter=typer.Argument(None))","text":"

Display all associations related to the current user.

Parameters:

Name Type Description Default filter str

Filter associations by approval status. Defaults to displaying all user associations.

typer.Argument(None) Source code in cli/medperf/commands/association/association.py
@app.command(\"ls\")\n@clean_except\ndef list(filter: Optional[str] = typer.Argument(None)):\n\"\"\"Display all associations related to the current user.\n    Args:\n        filter (str, optional): Filter associations by approval status.\n            Defaults to displaying all user associations.\n    \"\"\"\nListAssociations.run(filter)\n
"},{"location":"reference/commands/association/association/#commands.association.association.reject","title":"reject(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), dataset_uid=typer.Option(None, '--dataset', '-d', help='Dataset UID'), mlcube_uid=typer.Option(None, '--mlcube', '-m', help='MLCube UID'))","text":"

Rejects an association between a benchmark and a dataset or model mlcube

Parameters:

Name Type Description Default benchmark_uid int

Benchmark UID.

typer.Option(..., '--benchmark', '-b', help='Benchmark UID') dataset_uid int

Dataset UID.

typer.Option(None, '--dataset', '-d', help='Dataset UID') mlcube_uid int

Model MLCube UID.

typer.Option(None, '--mlcube', '-m', help='MLCube UID') Source code in cli/medperf/commands/association/association.py
@app.command(\"reject\")\n@clean_except\ndef reject(\nbenchmark_uid: int = typer.Option(..., \"--benchmark\", \"-b\", help=\"Benchmark UID\"),\ndataset_uid: int = typer.Option(None, \"--dataset\", \"-d\", help=\"Dataset UID\"),\nmlcube_uid: int = typer.Option(None, \"--mlcube\", \"-m\", help=\"MLCube UID\"),\n):\n\"\"\"Rejects an association between a benchmark and a dataset or model mlcube\n    Args:\n        benchmark_uid (int): Benchmark UID.\n        dataset_uid (int, optional): Dataset UID.\n        mlcube_uid (int, optional): Model MLCube UID.\n    \"\"\"\nApproval.run(benchmark_uid, Status.REJECTED, dataset_uid, mlcube_uid)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/association/association/#commands.association.association.set_priority","title":"set_priority(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), mlcube_uid=typer.Option(..., '--mlcube', '-m', help='MLCube UID'), priority=typer.Option(..., '--priority', '-p', help='Priority, an integer'))","text":"

Updates the priority of a benchmark-model association. Model priorities within a benchmark define which models need to be executed before others when this benchmark is run. A model with a higher priority is executed before a model with lower priority. The order of execution of models of the same priority is arbitrary.

Examples:

Assume there are three models of IDs (1,2,3), associated with a certain benchmark, all having priority = 0. - By setting the priority of model (2) to the value of 1, the client will make sure that model (2) is executed before models (1,3). - By setting the priority of model (1) to the value of -5, the client will make sure that models (2,3) are executed before model (1).

Parameters:

Name Type Description Default benchmark_uid int

Benchmark UID.

typer.Option(..., '--benchmark', '-b', help='Benchmark UID') mlcube_uid int

Model MLCube UID.

typer.Option(..., '--mlcube', '-m', help='MLCube UID') priority int

Priority, an integer

typer.Option(..., '--priority', '-p', help='Priority, an integer') Source code in cli/medperf/commands/association/association.py
@app.command(\"set_priority\")\n@clean_except\ndef set_priority(\nbenchmark_uid: int = typer.Option(..., \"--benchmark\", \"-b\", help=\"Benchmark UID\"),\nmlcube_uid: int = typer.Option(..., \"--mlcube\", \"-m\", help=\"MLCube UID\"),\npriority: int = typer.Option(..., \"--priority\", \"-p\", help=\"Priority, an integer\"),\n):\n\"\"\"Updates the priority of a benchmark-model association. Model priorities within\n    a benchmark define which models need to be executed before others when\n    this benchmark is run. A model with a higher priority is executed before\n    a model with lower priority. The order of execution of models of the same priority\n    is arbitrary.\n    Examples:\n    Assume there are three models of IDs (1,2,3), associated with a certain benchmark,\n    all having priority = 0.\n    - By setting the priority of model (2) to the value of 1, the client will make\n    sure that model (2) is executed before models (1,3).\n    - By setting the priority of model (1) to the value of -5, the client will make\n    sure that models (2,3) are executed before model (1).\n    Args:\n        benchmark_uid (int): Benchmark UID.\n        mlcube_uid (int): Model MLCube UID.\n        priority (int): Priority, an integer\n    \"\"\"\nAssociationPriority.run(benchmark_uid, mlcube_uid, priority)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/association/list/","title":"List","text":""},{"location":"reference/commands/association/list/#commands.association.list.ListAssociations","title":"ListAssociations","text":"Source code in cli/medperf/commands/association/list.py
class ListAssociations:\n@staticmethod\ndef run(filter: str = None):\n\"\"\"Get Pending association requests\"\"\"\ncomms = config.comms\nui = config.ui\ndset_assocs = comms.get_datasets_associations()\ncube_assocs = comms.get_cubes_associations()\n# Might be worth seeing if creating an association class that encapsulates\n# most of the logic here is useful\nassocs = dset_assocs + cube_assocs\nif filter:\nfilter = filter.upper()\nassocs = [assoc for assoc in assocs if assoc[\"approval_status\"] == filter]\nassocs_info = []\nfor assoc in assocs:\nassoc_info = (\nassoc.get(\"dataset\", None),\nassoc.get(\"model_mlcube\", None),\nassoc[\"benchmark\"],\nassoc[\"initiated_by\"],\nassoc[\"approval_status\"],\nassoc.get(\"priority\", None),\n# NOTE: We should find a better way to show priorities, since a priority\n# is better shown when listing cube associations only, of a specific\n# benchmark. Maybe this is resolved after we add a general filtering\n# feature to list commands.\n)\nassocs_info.append(assoc_info)\nheaders = [\n\"Dataset UID\",\n\"MLCube UID\",\n\"Benchmark UID\",\n\"Initiated by\",\n\"Status\",\n\"Priority\",\n]\ntab = tabulate(assocs_info, headers=headers)\nui.print(tab)\n
"},{"location":"reference/commands/association/list/#commands.association.list.ListAssociations.run","title":"run(filter=None) staticmethod","text":"

Get Pending association requests

Source code in cli/medperf/commands/association/list.py
@staticmethod\ndef run(filter: str = None):\n\"\"\"Get Pending association requests\"\"\"\ncomms = config.comms\nui = config.ui\ndset_assocs = comms.get_datasets_associations()\ncube_assocs = comms.get_cubes_associations()\n# Might be worth seeing if creating an association class that encapsulates\n# most of the logic here is useful\nassocs = dset_assocs + cube_assocs\nif filter:\nfilter = filter.upper()\nassocs = [assoc for assoc in assocs if assoc[\"approval_status\"] == filter]\nassocs_info = []\nfor assoc in assocs:\nassoc_info = (\nassoc.get(\"dataset\", None),\nassoc.get(\"model_mlcube\", None),\nassoc[\"benchmark\"],\nassoc[\"initiated_by\"],\nassoc[\"approval_status\"],\nassoc.get(\"priority\", None),\n# NOTE: We should find a better way to show priorities, since a priority\n# is better shown when listing cube associations only, of a specific\n# benchmark. Maybe this is resolved after we add a general filtering\n# feature to list commands.\n)\nassocs_info.append(assoc_info)\nheaders = [\n\"Dataset UID\",\n\"MLCube UID\",\n\"Benchmark UID\",\n\"Initiated by\",\n\"Status\",\n\"Priority\",\n]\ntab = tabulate(assocs_info, headers=headers)\nui.print(tab)\n
"},{"location":"reference/commands/association/priority/","title":"Priority","text":""},{"location":"reference/commands/association/priority/#commands.association.priority.AssociationPriority","title":"AssociationPriority","text":"Source code in cli/medperf/commands/association/priority.py
class AssociationPriority:\n@staticmethod\ndef run(benchmark_uid: int, mlcube_uid: int, priority: int):\n\"\"\"Sets priority for an association between a benchmark and an mlcube\n        Args:\n            benchmark_uid (int): Benchmark UID.\n            mlcube_uid (int): MLCube UID.\n            priority (int): priority value\n        \"\"\"\nassociated_cubes = Benchmark.get_models_uids(benchmark_uid)\nif mlcube_uid not in associated_cubes:\nraise InvalidArgumentError(\n\"The given mlcube doesn't exist or is not associated with the benchmark\"\n)\nconfig.comms.set_mlcube_association_priority(\nbenchmark_uid, mlcube_uid, priority\n)\n
"},{"location":"reference/commands/association/priority/#commands.association.priority.AssociationPriority.run","title":"run(benchmark_uid, mlcube_uid, priority) staticmethod","text":"

Sets priority for an association between a benchmark and an mlcube

Parameters:

Name Type Description Default benchmark_uid int

Benchmark UID.

required mlcube_uid int

MLCube UID.

required priority int

priority value

required Source code in cli/medperf/commands/association/priority.py
@staticmethod\ndef run(benchmark_uid: int, mlcube_uid: int, priority: int):\n\"\"\"Sets priority for an association between a benchmark and an mlcube\n    Args:\n        benchmark_uid (int): Benchmark UID.\n        mlcube_uid (int): MLCube UID.\n        priority (int): priority value\n    \"\"\"\nassociated_cubes = Benchmark.get_models_uids(benchmark_uid)\nif mlcube_uid not in associated_cubes:\nraise InvalidArgumentError(\n\"The given mlcube doesn't exist or is not associated with the benchmark\"\n)\nconfig.comms.set_mlcube_association_priority(\nbenchmark_uid, mlcube_uid, priority\n)\n
"},{"location":"reference/commands/auth/auth/","title":"Auth","text":""},{"location":"reference/commands/auth/auth/#commands.auth.auth.login","title":"login(email=typer.Option(None, '--email', '-e', help='The email associated with your account'))","text":"

Authenticate to be able to access the MedPerf server. A verification link will be provided and should be open in a browser to complete the login process.

Source code in cli/medperf/commands/auth/auth.py
@app.command(\"login\")\n@clean_except\ndef login(\nemail: str = typer.Option(\nNone, \"--email\", \"-e\", help=\"The email associated with your account\"\n)\n):\n\"\"\"Authenticate to be able to access the MedPerf server. A verification link will\n    be provided and should be open in a browser to complete the login process.\"\"\"\nLogin.run(email)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/auth/auth/#commands.auth.auth.logout","title":"logout()","text":"

Revoke the currently active login state.

Source code in cli/medperf/commands/auth/auth.py
@app.command(\"logout\")\n@clean_except\ndef logout():\n\"\"\"Revoke the currently active login state.\"\"\"\nLogout.run()\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/auth/auth/#commands.auth.auth.status","title":"status()","text":"

Shows the currently logged in user.

Source code in cli/medperf/commands/auth/auth.py
@app.command(\"status\")\n@clean_except\ndef status():\n\"\"\"Shows the currently logged in user.\"\"\"\nStatus.run()\n
"},{"location":"reference/commands/auth/auth/#commands.auth.auth.synapse_login","title":"synapse_login(token=typer.Option(None, '--token', '-t', help='Personal Access Token to login with'))","text":"

Login to the synapse server. Provide either a username and a password, or a token

Source code in cli/medperf/commands/auth/auth.py
@app.command(\"synapse_login\")\n@clean_except\ndef synapse_login(\ntoken: str = typer.Option(\nNone, \"--token\", \"-t\", help=\"Personal Access Token to login with\"\n),\n):\n\"\"\"Login to the synapse server.\n    Provide either a username and a password, or a token\n    \"\"\"\nSynapseLogin.run(token=token)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/auth/login/","title":"Login","text":""},{"location":"reference/commands/auth/login/#commands.auth.login.Login","title":"Login","text":"Source code in cli/medperf/commands/auth/login.py
class Login:\n@staticmethod\ndef run(email: str = None):\n\"\"\"Authenticate to be able to access the MedPerf server. A verification link will\n        be provided and should be open in a browser to complete the login process.\"\"\"\nraise_if_logged_in()\nif not email:\nemail = config.ui.prompt(\"Please type your email: \")\ntry:\nvalidate_email(email, check_deliverability=False)\nexcept EmailNotValidError as e:\nraise InvalidArgumentError(str(e))\nconfig.auth.login(email)\n
"},{"location":"reference/commands/auth/login/#commands.auth.login.Login.run","title":"run(email=None) staticmethod","text":"

Authenticate to be able to access the MedPerf server. A verification link will be provided and should be open in a browser to complete the login process.

Source code in cli/medperf/commands/auth/login.py
@staticmethod\ndef run(email: str = None):\n\"\"\"Authenticate to be able to access the MedPerf server. A verification link will\n    be provided and should be open in a browser to complete the login process.\"\"\"\nraise_if_logged_in()\nif not email:\nemail = config.ui.prompt(\"Please type your email: \")\ntry:\nvalidate_email(email, check_deliverability=False)\nexcept EmailNotValidError as e:\nraise InvalidArgumentError(str(e))\nconfig.auth.login(email)\n
"},{"location":"reference/commands/auth/logout/","title":"Logout","text":""},{"location":"reference/commands/auth/logout/#commands.auth.logout.Logout","title":"Logout","text":"Source code in cli/medperf/commands/auth/logout.py
class Logout:\n@staticmethod\ndef run():\n\"\"\"Revoke the currently active login state.\"\"\"\nconfig.auth.logout()\n
"},{"location":"reference/commands/auth/logout/#commands.auth.logout.Logout.run","title":"run() staticmethod","text":"

Revoke the currently active login state.

Source code in cli/medperf/commands/auth/logout.py
@staticmethod\ndef run():\n\"\"\"Revoke the currently active login state.\"\"\"\nconfig.auth.logout()\n
"},{"location":"reference/commands/auth/status/","title":"Status","text":""},{"location":"reference/commands/auth/status/#commands.auth.status.Status","title":"Status","text":"Source code in cli/medperf/commands/auth/status.py
class Status:\n@staticmethod\ndef run():\n\"\"\"Shows the currently logged in user.\"\"\"\naccount_info = read_user_account()\nif account_info is None:\nconfig.ui.print(\"You are not logged in\")\nreturn\nemail = account_info[\"email\"]\nconfig.ui.print(f\"Logged in user email address: {email}\")\n
"},{"location":"reference/commands/auth/status/#commands.auth.status.Status.run","title":"run() staticmethod","text":"

Shows the currently logged in user.

Source code in cli/medperf/commands/auth/status.py
@staticmethod\ndef run():\n\"\"\"Shows the currently logged in user.\"\"\"\naccount_info = read_user_account()\nif account_info is None:\nconfig.ui.print(\"You are not logged in\")\nreturn\nemail = account_info[\"email\"]\nconfig.ui.print(f\"Logged in user email address: {email}\")\n
"},{"location":"reference/commands/auth/synapse_login/","title":"Synapse login","text":""},{"location":"reference/commands/auth/synapse_login/#commands.auth.synapse_login.SynapseLogin","title":"SynapseLogin","text":"Source code in cli/medperf/commands/auth/synapse_login.py
class SynapseLogin:\n@classmethod\ndef run(cls, token: str = None):\n\"\"\"Login to the Synapse server. Must be done only once.\"\"\"\nif not token:\nmsg = (\n\"Please provide your Synapse Personal Access Token (PAT). \"\n\"You can generate a new PAT at \"\n\"https://www.synapse.org/#!PersonalAccessTokens:0\\n\"\n\"Synapse PAT: \"\n)\ntoken = config.ui.hidden_prompt(msg)\ncls.login_with_token(token)\n@classmethod\ndef login_with_token(cls, access_token=None):\n\"\"\"Login to the Synapse server. Must be done only once.\"\"\"\nsyn = synapseclient.Synapse()\ntry:\nsyn.login(authToken=access_token)\nexcept SynapseAuthenticationError as err:\nraise CommunicationAuthenticationError(\"Invalid Synapse credentials\") from err\n
"},{"location":"reference/commands/auth/synapse_login/#commands.auth.synapse_login.SynapseLogin.login_with_token","title":"login_with_token(access_token=None) classmethod","text":"

Login to the Synapse server. Must be done only once.

Source code in cli/medperf/commands/auth/synapse_login.py
@classmethod\ndef login_with_token(cls, access_token=None):\n\"\"\"Login to the Synapse server. Must be done only once.\"\"\"\nsyn = synapseclient.Synapse()\ntry:\nsyn.login(authToken=access_token)\nexcept SynapseAuthenticationError as err:\nraise CommunicationAuthenticationError(\"Invalid Synapse credentials\") from err\n
"},{"location":"reference/commands/auth/synapse_login/#commands.auth.synapse_login.SynapseLogin.run","title":"run(token=None) classmethod","text":"

Login to the Synapse server. Must be done only once.

Source code in cli/medperf/commands/auth/synapse_login.py
@classmethod\ndef run(cls, token: str = None):\n\"\"\"Login to the Synapse server. Must be done only once.\"\"\"\nif not token:\nmsg = (\n\"Please provide your Synapse Personal Access Token (PAT). \"\n\"You can generate a new PAT at \"\n\"https://www.synapse.org/#!PersonalAccessTokens:0\\n\"\n\"Synapse PAT: \"\n)\ntoken = config.ui.hidden_prompt(msg)\ncls.login_with_token(token)\n
"},{"location":"reference/commands/benchmark/associate/","title":"Associate","text":""},{"location":"reference/commands/benchmark/associate/#commands.benchmark.associate.AssociateBenchmark","title":"AssociateBenchmark","text":"Source code in cli/medperf/commands/benchmark/associate.py
class AssociateBenchmark:\n@classmethod\ndef run(\ncls,\nbenchmark_uid: int,\nmodel_uid: int,\ndata_uid: int,\napproved=False,\nno_cache=False,\n):\n\"\"\"Associates a dataset or model to the given benchmark\n        Args:\n            benchmark_uid (int): UID of benchmark to associate entities with\n            model_uid (int): UID of model to associate with benchmark\n            data_uid (int): UID of dataset to associate with benchmark\n            comms (Comms): Instance of Communications interface\n            ui (UI): Instance of UI interface\n            approved (bool): Skip approval step. Defaults to False\n        \"\"\"\ntoo_many_resources = data_uid and model_uid\nno_resource = data_uid is None and model_uid is None\nif no_resource or too_many_resources:\nraise InvalidArgumentError(\"Must provide either a dataset or mlcube\")\nif model_uid is not None:\nAssociateCube.run(\nmodel_uid, benchmark_uid, approved=approved, no_cache=no_cache\n)\nif data_uid is not None:\nAssociateDataset.run(\ndata_uid, benchmark_uid, approved=approved, no_cache=no_cache\n)\n
"},{"location":"reference/commands/benchmark/associate/#commands.benchmark.associate.AssociateBenchmark.run","title":"run(benchmark_uid, model_uid, data_uid, approved=False, no_cache=False) classmethod","text":"

Associates a dataset or model to the given benchmark

Parameters:

Name Type Description Default benchmark_uid int

UID of benchmark to associate entities with

required model_uid int

UID of model to associate with benchmark

required data_uid int

UID of dataset to associate with benchmark

required comms Comms

Instance of Communications interface

required ui UI

Instance of UI interface

required approved bool

Skip approval step. Defaults to False

False Source code in cli/medperf/commands/benchmark/associate.py
@classmethod\ndef run(\ncls,\nbenchmark_uid: int,\nmodel_uid: int,\ndata_uid: int,\napproved=False,\nno_cache=False,\n):\n\"\"\"Associates a dataset or model to the given benchmark\n    Args:\n        benchmark_uid (int): UID of benchmark to associate entities with\n        model_uid (int): UID of model to associate with benchmark\n        data_uid (int): UID of dataset to associate with benchmark\n        comms (Comms): Instance of Communications interface\n        ui (UI): Instance of UI interface\n        approved (bool): Skip approval step. Defaults to False\n    \"\"\"\ntoo_many_resources = data_uid and model_uid\nno_resource = data_uid is None and model_uid is None\nif no_resource or too_many_resources:\nraise InvalidArgumentError(\"Must provide either a dataset or mlcube\")\nif model_uid is not None:\nAssociateCube.run(\nmodel_uid, benchmark_uid, approved=approved, no_cache=no_cache\n)\nif data_uid is not None:\nAssociateDataset.run(\ndata_uid, benchmark_uid, approved=approved, no_cache=no_cache\n)\n
"},{"location":"reference/commands/benchmark/benchmark/","title":"Benchmark","text":""},{"location":"reference/commands/benchmark/benchmark/#commands.benchmark.benchmark.associate","title":"associate(benchmark_uid=typer.Option(..., '--benchmark_uid', '-b', help='UID of benchmark to associate with'), model_uid=typer.Option(None, '--model_uid', '-m', help='UID of model MLCube to associate'), dataset_uid=typer.Option(None, '--data_uid', '-d', help='Server UID of registered dataset to associate'), approval=typer.Option(False, '-y', help='Skip approval step'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist'))","text":"

Associates a benchmark with a given mlcube or dataset. Only one option at a time.

Source code in cli/medperf/commands/benchmark/benchmark.py
@app.command(\"associate\")\n@clean_except\ndef associate(\nbenchmark_uid: int = typer.Option(\n..., \"--benchmark_uid\", \"-b\", help=\"UID of benchmark to associate with\"\n),\nmodel_uid: int = typer.Option(\nNone, \"--model_uid\", \"-m\", help=\"UID of model MLCube to associate\"\n),\ndataset_uid: int = typer.Option(\nNone, \"--data_uid\", \"-d\", help=\"Server UID of registered dataset to associate\"\n),\napproval: bool = typer.Option(False, \"-y\", help=\"Skip approval step\"),\nno_cache: bool = typer.Option(\nFalse,\n\"--no-cache\",\nhelp=\"Execute the test even if results already exist\",\n),\n):\n\"\"\"Associates a benchmark with a given mlcube or dataset. Only one option at a time.\"\"\"\nAssociateBenchmark.run(\nbenchmark_uid, model_uid, dataset_uid, approved=approval, no_cache=no_cache\n)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/benchmark/benchmark/#commands.benchmark.benchmark.list","title":"list(local=typer.Option(False, '--local', help='Get local benchmarks'), mine=typer.Option(False, '--mine', help='Get current-user benchmarks'))","text":"

List benchmarks stored locally and remotely from the user

Source code in cli/medperf/commands/benchmark/benchmark.py
@app.command(\"ls\")\n@clean_except\ndef list(\nlocal: bool = typer.Option(False, \"--local\", help=\"Get local benchmarks\"),\nmine: bool = typer.Option(False, \"--mine\", help=\"Get current-user benchmarks\"),\n):\n\"\"\"List benchmarks stored locally and remotely from the user\"\"\"\nEntityList.run(\nBenchmark,\nfields=[\"UID\", \"Name\", \"Description\", \"State\", \"Approval Status\", \"Registered\"],\nlocal_only=local,\nmine_only=mine,\n)\n
"},{"location":"reference/commands/benchmark/benchmark/#commands.benchmark.benchmark.run","title":"run(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='UID of the desired benchmark'), data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), file=typer.Option(None, '--models-from-file', '-f', help='A file containing the model UIDs to be executed.\\n\\n The file should contain a single line as a list of\\n\\n comma-separated integers corresponding to the model UIDs'), ignore_model_errors=typer.Option(False, '--ignore-model-errors', help='Ignore failing model cubes, allowing for possibly submitting partial results'), no_cache=typer.Option(False, '--no-cache', help='Execute even if results already exist'))","text":"

Runs the benchmark execution step for a given benchmark, prepared dataset and model

Source code in cli/medperf/commands/benchmark/benchmark.py
@app.command(\"run\")\n@clean_except\ndef run(\nbenchmark_uid: int = typer.Option(\n..., \"--benchmark\", \"-b\", help=\"UID of the desired benchmark\"\n),\ndata_uid: int = typer.Option(\n..., \"--data_uid\", \"-d\", help=\"Registered Dataset UID\"\n),\nfile: str = typer.Option(\nNone,\n\"--models-from-file\",\n\"-f\",\nhelp=\"\"\"A file containing the model UIDs to be executed.\\n\n        The file should contain a single line as a list of\\n\n        comma-separated integers corresponding to the model UIDs\"\"\",\n),\nignore_model_errors: bool = typer.Option(\nFalse,\n\"--ignore-model-errors\",\nhelp=\"Ignore failing model cubes, allowing for possibly submitting partial results\",\n),\nno_cache: bool = typer.Option(\nFalse,\n\"--no-cache\",\nhelp=\"Execute even if results already exist\",\n),\n):\n\"\"\"Runs the benchmark execution step for a given benchmark, prepared dataset and model\"\"\"\nBenchmarkExecution.run(\nbenchmark_uid,\ndata_uid,\nmodels_uids=None,\nno_cache=no_cache,\nmodels_input_file=file,\nignore_model_errors=ignore_model_errors,\nshow_summary=True,\nignore_failed_experiments=True,\n)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/benchmark/benchmark/#commands.benchmark.benchmark.submit","title":"submit(name=typer.Option(..., '--name', '-n', help='Name of the benchmark'), description=typer.Option(..., '--description', '-d', help='Description of the benchmark'), docs_url=typer.Option('', '--docs-url', '-u', help='URL to documentation'), demo_url=typer.Option(..., '--demo-url', help='Identifier to download the demonstration dataset tarball file.\\n\\n See `medperf mlcube submit --help` for more information'), demo_hash=typer.Option('', '--demo-hash', help='Hash of demonstration dataset tarball file'), data_preparation_mlcube=typer.Option(..., '--data-preparation-mlcube', '-p', help='Data Preparation MLCube UID'), reference_model_mlcube=typer.Option(..., '--reference-model-mlcube', '-m', help='Reference Model MLCube UID'), evaluator_mlcube=typer.Option(..., '--evaluator-mlcube', '-e', help='Evaluator MLCube UID'), skip_data_preparation_step=typer.Option(False, '--skip-demo-data-preparation', help='Use this flag if the demo dataset is already prepared'), operational=typer.Option(False, '--operational', help='Submit the Benchmark as OPERATIONAL'))","text":"

Submits a new benchmark to the platform

Source code in cli/medperf/commands/benchmark/benchmark.py
@app.command(\"submit\")\n@clean_except\ndef submit(\nname: str = typer.Option(..., \"--name\", \"-n\", help=\"Name of the benchmark\"),\ndescription: str = typer.Option(\n..., \"--description\", \"-d\", help=\"Description of the benchmark\"\n),\ndocs_url: str = typer.Option(\"\", \"--docs-url\", \"-u\", help=\"URL to documentation\"),\ndemo_url: str = typer.Option(\n...,\n\"--demo-url\",\nhelp=\"\"\"Identifier to download the demonstration dataset tarball file.\\n\n        See `medperf mlcube submit --help` for more information\"\"\",\n),\ndemo_hash: str = typer.Option(\n\"\", \"--demo-hash\", help=\"Hash of demonstration dataset tarball file\"\n),\ndata_preparation_mlcube: int = typer.Option(\n..., \"--data-preparation-mlcube\", \"-p\", help=\"Data Preparation MLCube UID\"\n),\nreference_model_mlcube: int = typer.Option(\n..., \"--reference-model-mlcube\", \"-m\", help=\"Reference Model MLCube UID\"\n),\nevaluator_mlcube: int = typer.Option(\n..., \"--evaluator-mlcube\", \"-e\", help=\"Evaluator MLCube UID\"\n),\nskip_data_preparation_step: bool = typer.Option(\nFalse,\n\"--skip-demo-data-preparation\",\nhelp=\"Use this flag if the demo dataset is already prepared\",\n),\noperational: bool = typer.Option(\nFalse,\n\"--operational\",\nhelp=\"Submit the Benchmark as OPERATIONAL\",\n),\n):\n\"\"\"Submits a new benchmark to the platform\"\"\"\nbenchmark_info = {\n\"name\": name,\n\"description\": description,\n\"docs_url\": docs_url,\n\"demo_dataset_tarball_url\": demo_url,\n\"demo_dataset_tarball_hash\": demo_hash,\n\"data_preparation_mlcube\": data_preparation_mlcube,\n\"reference_model_mlcube\": reference_model_mlcube,\n\"data_evaluator_mlcube\": evaluator_mlcube,\n\"state\": \"OPERATION\" if operational else \"DEVELOPMENT\",\n}\nSubmitBenchmark.run(\nbenchmark_info,\nskip_data_preparation_step=skip_data_preparation_step,\n)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/benchmark/benchmark/#commands.benchmark.benchmark.view","title":"view(entity_id=typer.Argument(None, help='Benchmark ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local benchmarks if benchmark ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user benchmarks if benchmark ID is not provided'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed'))","text":"

Displays the information of one or more benchmarks

Source code in cli/medperf/commands/benchmark/benchmark.py
@app.command(\"view\")\n@clean_except\ndef view(\nentity_id: Optional[int] = typer.Argument(None, help=\"Benchmark ID\"),\nformat: str = typer.Option(\n\"yaml\",\n\"-f\",\n\"--format\",\nhelp=\"Format to display contents. Available formats: [yaml, json]\",\n),\nlocal: bool = typer.Option(\nFalse,\n\"--local\",\nhelp=\"Display local benchmarks if benchmark ID is not provided\",\n),\nmine: bool = typer.Option(\nFalse,\n\"--mine\",\nhelp=\"Display current-user benchmarks if benchmark ID is not provided\",\n),\noutput: str = typer.Option(\nNone,\n\"--output\",\n\"-o\",\nhelp=\"Output file to store contents. If not provided, the output will be displayed\",\n),\n):\n\"\"\"Displays the information of one or more benchmarks\"\"\"\nEntityView.run(entity_id, Benchmark, format, local, mine, output)\n
"},{"location":"reference/commands/benchmark/submit/","title":"Submit","text":""},{"location":"reference/commands/benchmark/submit/#commands.benchmark.submit.SubmitBenchmark","title":"SubmitBenchmark","text":"Source code in cli/medperf/commands/benchmark/submit.py
class SubmitBenchmark:\n@classmethod\ndef run(\ncls,\nbenchmark_info: dict,\nno_cache: bool = True,\nskip_data_preparation_step: bool = False,\n):\n\"\"\"Submits a new cube to the medperf platform\n        Args:\n            benchmark_info (dict): benchmark information\n                expected keys:\n                    name (str): benchmark name\n                    description (str): benchmark description\n                    docs_url (str): benchmark documentation url\n                    demo_url (str): benchmark demo dataset url\n                    demo_hash (str): benchmark demo dataset hash\n                    data_preparation_mlcube (int): benchmark data preparation mlcube uid\n                    reference_model_mlcube (int): benchmark reference model mlcube uid\n                    evaluator_mlcube (int): benchmark data evaluator mlcube uid\n        \"\"\"\nui = config.ui\nsubmission = cls(benchmark_info, no_cache, skip_data_preparation_step)\nwith ui.interactive():\nui.text = \"Getting additional information\"\nsubmission.get_extra_information()\nui.print(\"> Completed benchmark registration information\")\nui.text = \"Submitting Benchmark to MedPerf\"\nupdated_benchmark_body = submission.submit()\nui.print(\"Uploaded\")\nsubmission.to_permanent_path(updated_benchmark_body)\nsubmission.write(updated_benchmark_body)\ndef __init__(\nself,\nbenchmark_info: dict,\nno_cache: bool = True,\nskip_data_preparation_step: bool = False,\n):\nself.ui = config.ui\nself.bmk = Benchmark(**benchmark_info)\nself.no_cache = no_cache\nself.skip_data_preparation_step = skip_data_preparation_step\nself.bmk.metadata[\"demo_dataset_already_prepared\"] = skip_data_preparation_step\nconfig.tmp_paths.append(self.bmk.path)\ndef get_extra_information(self):\n\"\"\"Retrieves information that must be populated automatically,\n        like hash, generated uid and test results\n        \"\"\"\nbmk_demo_url = self.bmk.demo_dataset_tarball_url\nbmk_demo_hash = self.bmk.demo_dataset_tarball_hash\ntry:\n_, demo_hash = resources.get_benchmark_demo_dataset(\nbmk_demo_url, bmk_demo_hash\n)\nexcept InvalidEntityError as e:\nraise InvalidEntityError(f\"Demo dataset {bmk_demo_url}: {e}\")\nself.bmk.demo_dataset_tarball_hash = demo_hash\ndemo_uid, results = self.run_compatibility_test()\nself.bmk.demo_dataset_generated_uid = demo_uid\nself.bmk.metadata[\"results\"] = results\ndef run_compatibility_test(self):\n\"\"\"Runs a compatibility test to ensure elements are compatible,\n        and to extract additional information required for submission\n        \"\"\"\nself.ui.print(\"Running compatibility test\")\nself.bmk.write()\ndata_uid, results = CompatibilityTestExecution.run(\nbenchmark=self.bmk.generated_uid,\nno_cache=self.no_cache,\nskip_data_preparation_step=self.skip_data_preparation_step,\n)\nreturn data_uid, results\ndef submit(self):\nupdated_body = self.bmk.upload()\nreturn updated_body\ndef to_permanent_path(self, bmk_dict: dict):\n\"\"\"Renames the temporary benchmark submission to a permanent one\n        Args:\n            bmk_dict (dict): dictionary containing updated information of the submitted benchmark\n        \"\"\"\nold_bmk_loc = self.bmk.path\nupdated_bmk = Benchmark(**bmk_dict)\nnew_bmk_loc = updated_bmk.path\nremove_path(new_bmk_loc)\nos.rename(old_bmk_loc, new_bmk_loc)\ndef write(self, updated_body):\nbmk = Benchmark(**updated_body)\nbmk.write()\n
"},{"location":"reference/commands/benchmark/submit/#commands.benchmark.submit.SubmitBenchmark.get_extra_information","title":"get_extra_information()","text":"

Retrieves information that must be populated automatically, like hash, generated uid and test results

Source code in cli/medperf/commands/benchmark/submit.py
def get_extra_information(self):\n\"\"\"Retrieves information that must be populated automatically,\n    like hash, generated uid and test results\n    \"\"\"\nbmk_demo_url = self.bmk.demo_dataset_tarball_url\nbmk_demo_hash = self.bmk.demo_dataset_tarball_hash\ntry:\n_, demo_hash = resources.get_benchmark_demo_dataset(\nbmk_demo_url, bmk_demo_hash\n)\nexcept InvalidEntityError as e:\nraise InvalidEntityError(f\"Demo dataset {bmk_demo_url}: {e}\")\nself.bmk.demo_dataset_tarball_hash = demo_hash\ndemo_uid, results = self.run_compatibility_test()\nself.bmk.demo_dataset_generated_uid = demo_uid\nself.bmk.metadata[\"results\"] = results\n
"},{"location":"reference/commands/benchmark/submit/#commands.benchmark.submit.SubmitBenchmark.run","title":"run(benchmark_info, no_cache=True, skip_data_preparation_step=False) classmethod","text":"

Submits a new cube to the medperf platform

Parameters:

Name Type Description Default benchmark_info dict

benchmark information expected keys: name (str): benchmark name description (str): benchmark description docs_url (str): benchmark documentation url demo_url (str): benchmark demo dataset url demo_hash (str): benchmark demo dataset hash data_preparation_mlcube (int): benchmark data preparation mlcube uid reference_model_mlcube (int): benchmark reference model mlcube uid evaluator_mlcube (int): benchmark data evaluator mlcube uid

required Source code in cli/medperf/commands/benchmark/submit.py
@classmethod\ndef run(\ncls,\nbenchmark_info: dict,\nno_cache: bool = True,\nskip_data_preparation_step: bool = False,\n):\n\"\"\"Submits a new cube to the medperf platform\n    Args:\n        benchmark_info (dict): benchmark information\n            expected keys:\n                name (str): benchmark name\n                description (str): benchmark description\n                docs_url (str): benchmark documentation url\n                demo_url (str): benchmark demo dataset url\n                demo_hash (str): benchmark demo dataset hash\n                data_preparation_mlcube (int): benchmark data preparation mlcube uid\n                reference_model_mlcube (int): benchmark reference model mlcube uid\n                evaluator_mlcube (int): benchmark data evaluator mlcube uid\n    \"\"\"\nui = config.ui\nsubmission = cls(benchmark_info, no_cache, skip_data_preparation_step)\nwith ui.interactive():\nui.text = \"Getting additional information\"\nsubmission.get_extra_information()\nui.print(\"> Completed benchmark registration information\")\nui.text = \"Submitting Benchmark to MedPerf\"\nupdated_benchmark_body = submission.submit()\nui.print(\"Uploaded\")\nsubmission.to_permanent_path(updated_benchmark_body)\nsubmission.write(updated_benchmark_body)\n
"},{"location":"reference/commands/benchmark/submit/#commands.benchmark.submit.SubmitBenchmark.run_compatibility_test","title":"run_compatibility_test()","text":"

Runs a compatibility test to ensure elements are compatible, and to extract additional information required for submission

Source code in cli/medperf/commands/benchmark/submit.py
def run_compatibility_test(self):\n\"\"\"Runs a compatibility test to ensure elements are compatible,\n    and to extract additional information required for submission\n    \"\"\"\nself.ui.print(\"Running compatibility test\")\nself.bmk.write()\ndata_uid, results = CompatibilityTestExecution.run(\nbenchmark=self.bmk.generated_uid,\nno_cache=self.no_cache,\nskip_data_preparation_step=self.skip_data_preparation_step,\n)\nreturn data_uid, results\n
"},{"location":"reference/commands/benchmark/submit/#commands.benchmark.submit.SubmitBenchmark.to_permanent_path","title":"to_permanent_path(bmk_dict)","text":"

Renames the temporary benchmark submission to a permanent one

Parameters:

Name Type Description Default bmk_dict dict

dictionary containing updated information of the submitted benchmark

required Source code in cli/medperf/commands/benchmark/submit.py
def to_permanent_path(self, bmk_dict: dict):\n\"\"\"Renames the temporary benchmark submission to a permanent one\n    Args:\n        bmk_dict (dict): dictionary containing updated information of the submitted benchmark\n    \"\"\"\nold_bmk_loc = self.bmk.path\nupdated_bmk = Benchmark(**bmk_dict)\nnew_bmk_loc = updated_bmk.path\nremove_path(new_bmk_loc)\nos.rename(old_bmk_loc, new_bmk_loc)\n
"},{"location":"reference/commands/compatibility_test/compatibility_test/","title":"Compatibility test","text":""},{"location":"reference/commands/compatibility_test/compatibility_test/#commands.compatibility_test.compatibility_test.list","title":"list()","text":"

List previously executed tests reports.

Source code in cli/medperf/commands/compatibility_test/compatibility_test.py
@app.command(\"ls\")\n@clean_except\ndef list():\n\"\"\"List previously executed tests reports.\"\"\"\nEntityList.run(TestReport, fields=[\"UID\", \"Data Source\", \"Model\", \"Evaluator\"])\n
"},{"location":"reference/commands/compatibility_test/compatibility_test/#commands.compatibility_test.compatibility_test.run","title":"run(benchmark_uid=typer.Option(None, '--benchmark', '-b', help='UID of the benchmark to test. Optional'), data_uid=typer.Option(None, '--data_uid', '-d', help='Prepared Dataset UID. Used for dataset testing. Optional. Defaults to benchmark demo dataset.'), demo_dataset_url=typer.Option(None, '--demo_dataset_url', help='Identifier to download the demonstration dataset tarball file.\\n\\n See `medperf mlcube submit --help` for more information'), demo_dataset_hash=typer.Option(None, '--demo_dataset_hash', help='Hash of the demo dataset, if provided.'), data_path=typer.Option(None, '--data_path', help='Path to raw input data.'), labels_path=typer.Option(None, '--labels_path', help='Path to the labels of the raw input data, if provided.'), data_prep=typer.Option(None, '--data_preparation', '-p', help='UID or local path to the data preparation mlcube. Optional. Defaults to benchmark data preparation mlcube.'), model=typer.Option(None, '--model', '-m', help='UID or local path to the model mlcube. Optional. Defaults to benchmark reference mlcube.'), evaluator=typer.Option(None, '--evaluator', '-e', help='UID or local path to the evaluator mlcube. Optional. Defaults to benchmark evaluator mlcube'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist'), offline=typer.Option(False, '--offline', help='Execute the test without connecting to the MedPerf server.'), skip_data_preparation_step=typer.Option(False, '--skip-demo-data-preparation', help='Use this flag if the passed demo dataset or data path is already prepared'))","text":"

Executes a compatibility test for a determined benchmark. Can test prepared and unprepared datasets, remote and local models independently.

Source code in cli/medperf/commands/compatibility_test/compatibility_test.py
@app.command(\"run\")\n@clean_except\ndef run(\nbenchmark_uid: int = typer.Option(\nNone, \"--benchmark\", \"-b\", help=\"UID of the benchmark to test. Optional\"\n),\ndata_uid: str = typer.Option(\nNone,\n\"--data_uid\",\n\"-d\",\nhelp=\"Prepared Dataset UID. Used for dataset testing. Optional. Defaults to benchmark demo dataset.\",\n),\ndemo_dataset_url: str = typer.Option(\nNone,\n\"--demo_dataset_url\",\nhelp=\"\"\"Identifier to download the demonstration dataset tarball file.\\n\n            See `medperf mlcube submit --help` for more information\"\"\",\n),\ndemo_dataset_hash: str = typer.Option(\nNone, \"--demo_dataset_hash\", help=\"Hash of the demo dataset, if provided.\"\n),\ndata_path: str = typer.Option(None, \"--data_path\", help=\"Path to raw input data.\"),\nlabels_path: str = typer.Option(\nNone,\n\"--labels_path\",\nhelp=\"Path to the labels of the raw input data, if provided.\",\n),\ndata_prep: str = typer.Option(\nNone,\n\"--data_preparation\",\n\"-p\",\nhelp=\"UID or local path to the data preparation mlcube. Optional. Defaults to benchmark data preparation mlcube.\",\n),\nmodel: str = typer.Option(\nNone,\n\"--model\",\n\"-m\",\nhelp=\"UID or local path to the model mlcube. Optional. Defaults to benchmark reference mlcube.\",\n),\nevaluator: str = typer.Option(\nNone,\n\"--evaluator\",\n\"-e\",\nhelp=\"UID or local path to the evaluator mlcube. Optional. Defaults to benchmark evaluator mlcube\",\n),\nno_cache: bool = typer.Option(\nFalse, \"--no-cache\", help=\"Execute the test even if results already exist\"\n),\noffline: bool = typer.Option(\nFalse,\n\"--offline\",\nhelp=\"Execute the test without connecting to the MedPerf server.\",\n),\nskip_data_preparation_step: bool = typer.Option(\nFalse,\n\"--skip-demo-data-preparation\",\nhelp=\"Use this flag if the passed demo dataset or data path is already prepared\",\n),\n):\n\"\"\"\n    Executes a compatibility test for a determined benchmark.\n    Can test prepared and unprepared datasets, remote and local models independently.\n    \"\"\"\nCompatibilityTestExecution.run(\nbenchmark_uid,\ndata_prep,\nmodel,\nevaluator,\ndata_path,\nlabels_path,\ndemo_dataset_url,\ndemo_dataset_hash,\ndata_uid,\nno_cache=no_cache,\noffline=offline,\nskip_data_preparation_step=skip_data_preparation_step,\n)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/compatibility_test/compatibility_test/#commands.compatibility_test.compatibility_test.view","title":"view(entity_id=typer.Argument(None, help='Test report ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed'))","text":"

Displays the information of one or more test reports

Source code in cli/medperf/commands/compatibility_test/compatibility_test.py
@app.command(\"view\")\n@clean_except\ndef view(\nentity_id: Optional[str] = typer.Argument(None, help=\"Test report ID\"),\nformat: str = typer.Option(\n\"yaml\",\n\"-f\",\n\"--format\",\nhelp=\"Format to display contents. Available formats: [yaml, json]\",\n),\noutput: str = typer.Option(\nNone,\n\"--output\",\n\"-o\",\nhelp=\"Output file to store contents. If not provided, the output will be displayed\",\n),\n):\n\"\"\"Displays the information of one or more test reports\"\"\"\nEntityView.run(entity_id, TestReport, format, output=output)\n
"},{"location":"reference/commands/compatibility_test/run/","title":"Run","text":""},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution","title":"CompatibilityTestExecution","text":"Source code in cli/medperf/commands/compatibility_test/run.py
class CompatibilityTestExecution:\n@classmethod\ndef run(\ncls,\nbenchmark: int = None,\ndata_prep: str = None,\nmodel: str = None,\nevaluator: str = None,\ndata_path: str = None,\nlabels_path: str = None,\ndemo_dataset_url: str = None,\ndemo_dataset_hash: str = None,\ndata_uid: str = None,\nno_cache: bool = False,\noffline: bool = False,\nskip_data_preparation_step: bool = False,\n) -> (str, dict):\n\"\"\"Execute a test workflow. Components of a complete workflow should be passed.\n        When only the benchmark is provided, it implies the following workflow will be used:\n        - the benchmark's demo dataset is used as the raw data\n        - the benchmark's data preparation cube is used\n        - the benchmark's reference model cube is used\n        - the benchmark's metrics cube is used\n        Overriding benchmark's components:\n        - The data prepration, model, and metrics cubes can be overriden by specifying a cube either\n        as an integer (registered) or a path (local). The path can refer either to the mlcube config\n        file or to the mlcube directory containing the mlcube config file.\n        - Instead of using the demo dataset of the benchmark, The input raw data can be overriden by providing:\n            - a demo dataset url and its hash\n            - data path and labels path\n        - A prepared dataset can be directly used. In this case the data preparator cube is never used.\n        The prepared data can be provided by either specifying an integer (registered) or a hash of a\n        locally prepared dataset.\n        Whether the benchmark is provided or not, the command will fail either if the user fails to\n        provide a valid complete workflow, or if the user provided extra redundant parameters.\n        Args:\n            benchmark (int, optional): Benchmark to run the test workflow for\n            data_prep (str, optional): data preparation mlcube uid or local path.\n            model (str, optional): model mlcube uid or local path.\n            evaluator (str, optional): evaluator mlcube uid or local path.\n            data_path (str, optional): path to a local raw data\n            labels_path (str, optional): path to the labels of the local raw data\n            demo_dataset_url (str, optional): Identifier to download the demonstration dataset tarball file.\\n\n            See `medperf mlcube submit --help` for more information\n            demo_dataset_hash (str, optional): The hash of the demo dataset tarball file\n            data_uid (str, optional): A prepared dataset UID\n            no_cache (bool): Whether to ignore cached results of the test execution. Defaults to False.\n            offline (bool): Whether to disable communication to the MedPerf server and rely only on\n            local copies of the server assets. Defaults to False.\n        Returns:\n            (str): Prepared Dataset UID used for the test. Could be the one provided or a generated one.\n            (dict): Results generated by the test.\n        \"\"\"\nlogging.info(\"Starting test execution\")\ntest_exec = cls(\nbenchmark,\ndata_prep,\nmodel,\nevaluator,\ndata_path,\nlabels_path,\ndemo_dataset_url,\ndemo_dataset_hash,\ndata_uid,\nno_cache,\noffline,\nskip_data_preparation_step,\n)\ntest_exec.validate()\ntest_exec.set_data_source()\ntest_exec.process_benchmark()\ntest_exec.prepare_cubes()\ntest_exec.prepare_dataset()\ntest_exec.initialize_report()\nresults = test_exec.cached_results()\nif results is None:\nresults = test_exec.execute()\ntest_exec.write(results)\nelse:\nlogging.info(\"Existing results are found. Test would not be re-executed.\")\nlogging.debug(f\"Existing results: {results}\")\nreturn test_exec.data_uid, results\ndef __init__(\nself,\nbenchmark: int = None,\ndata_prep: str = None,\nmodel: str = None,\nevaluator: str = None,\ndata_path: str = None,\nlabels_path: str = None,\ndemo_dataset_url: str = None,\ndemo_dataset_hash: str = None,\ndata_uid: str = None,\nno_cache: bool = False,\noffline: bool = False,\nskip_data_preparation_step: bool = False,\n):\nself.benchmark_uid = benchmark\nself.data_prep = data_prep\nself.model = model\nself.evaluator = evaluator\nself.data_path = data_path\nself.labels_path = labels_path\nself.demo_dataset_url = demo_dataset_url\nself.demo_dataset_hash = demo_dataset_hash\nself.data_uid = data_uid\nself.no_cache = no_cache\nself.offline = offline\nself.skip_data_preparation_step = skip_data_preparation_step\n# This property will be set to either \"path\", \"demo\", \"prepared\", or \"benchmark\"\nself.data_source = None\nself.dataset = None\nself.model_cube = None\nself.evaluator_cube = None\nself.validator = CompatibilityTestParamsValidator(\nself.benchmark_uid,\nself.data_prep,\nself.model,\nself.evaluator,\nself.data_path,\nself.labels_path,\nself.demo_dataset_url,\nself.demo_dataset_hash,\nself.data_uid,\n)\ndef validate(self):\nself.validator.validate()\ndef set_data_source(self):\nself.data_source = self.validator.get_data_source()\ndef process_benchmark(self):\n\"\"\"Process the benchmark input if given. Sets the needed parameters from\n        the benchmark.\"\"\"\nif not self.benchmark_uid:\nreturn\nbenchmark = Benchmark.get(self.benchmark_uid, local_only=self.offline)\nif self.data_source != \"prepared\":\nself.data_prep = self.data_prep or benchmark.data_preparation_mlcube\nself.model = self.model or benchmark.reference_model_mlcube\nself.evaluator = self.evaluator or benchmark.data_evaluator_mlcube\nif self.data_source == \"benchmark\":\nself.demo_dataset_url = benchmark.demo_dataset_tarball_url\nself.demo_dataset_hash = benchmark.demo_dataset_tarball_hash\nself.skip_data_preparation_step = benchmark.metadata.get(\n\"demo_dataset_already_prepared\", False\n)\ndef prepare_cubes(self):\n\"\"\"Prepares the mlcubes. If the provided mlcube is a path, it will create\n        a temporary uid and link the cube path to the medperf storage path.\"\"\"\nif self.data_source != \"prepared\":\nlogging.info(f\"Establishing the data preparation cube: {self.data_prep}\")\nself.data_prep = prepare_cube(self.data_prep)\nlogging.info(f\"Establishing the model cube: {self.model}\")\nself.model = prepare_cube(self.model)\nlogging.info(f\"Establishing the evaluator cube: {self.evaluator}\")\nself.evaluator = prepare_cube(self.evaluator)\nself.model_cube = get_cube(self.model, \"Model\", local_only=self.offline)\nself.evaluator_cube = get_cube(\nself.evaluator, \"Evaluator\", local_only=self.offline\n)\ndef prepare_dataset(self):\n\"\"\"Assigns the data_uid used for testing and retrieves the dataset.\n        If the data is not prepared, it calls the data preparation step\n        on the given local data path or using a remote demo dataset.\"\"\"\nlogging.info(\"Establishing data_uid for test execution\")\nif self.data_source != \"prepared\":\nif self.data_source == \"path\":\ndata_path, labels_path = self.data_path, self.labels_path\n# TODO: this has to be redesigned. Compatibility tests command\n#       is starting to have a lot of input arguments. For now\n#       let's not support accepting a metadata path\nmetadata_path = None\nelse:\ndata_path, labels_path, metadata_path = download_demo_data(\nself.demo_dataset_url, self.demo_dataset_hash\n)\nself.data_uid = create_test_dataset(\ndata_path,\nlabels_path,\nmetadata_path,\nself.data_prep,\nself.skip_data_preparation_step,\n)\nself.dataset = Dataset.get(self.data_uid, local_only=self.offline)\ndef initialize_report(self):\n\"\"\"Initializes an instance of `TestReport` to hold the current test information.\"\"\"\nreport_data = {\n\"demo_dataset_url\": self.demo_dataset_url,\n\"demo_dataset_hash\": self.demo_dataset_hash,\n\"data_path\": self.data_path,\n\"labels_path\": self.labels_path,\n\"prepared_data_hash\": self.data_uid,\n\"data_preparation_mlcube\": self.data_prep,\n\"model\": self.model,\n\"data_evaluator_mlcube\": self.evaluator,\n}\nself.report = TestReport(**report_data)\ndef cached_results(self):\n\"\"\"checks the existance of, and retrieves if possible, the compatibility test\n        result. This method is called prior to the test execution.\n        Returns:\n            (dict|None): None if the results does not exist or if self.no_cache is True,\n            otherwise it returns the found results.\n        \"\"\"\nif self.no_cache:\nreturn\nuid = self.report.generated_uid\ntry:\nreport = TestReport.get(uid)\nexcept InvalidArgumentError:\nreturn\nlogging.info(f\"Existing report {uid} was detected.\")\nlogging.info(\"The compatibilty test will not be re-executed.\")\nreturn report.results\ndef execute(self):\n\"\"\"Runs the test execution flow and returns the results\n        Returns:\n            dict: returns the results of the test execution.\n        \"\"\"\nexecution_summary = Execution.run(\ndataset=self.dataset,\nmodel=self.model_cube,\nevaluator=self.evaluator_cube,\nignore_model_errors=False,\n)\nreturn execution_summary[\"results\"]\ndef write(self, results):\n\"\"\"Writes a report of the test execution to the disk\n        Args:\n            results (dict): the results of the test execution\n        \"\"\"\nself.report.set_results(results)\nself.report.write()\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.cached_results","title":"cached_results()","text":"

checks the existance of, and retrieves if possible, the compatibility test result. This method is called prior to the test execution.

Returns:

Type Description dict | None

None if the results does not exist or if self.no_cache is True,

otherwise it returns the found results.

Source code in cli/medperf/commands/compatibility_test/run.py
def cached_results(self):\n\"\"\"checks the existance of, and retrieves if possible, the compatibility test\n    result. This method is called prior to the test execution.\n    Returns:\n        (dict|None): None if the results does not exist or if self.no_cache is True,\n        otherwise it returns the found results.\n    \"\"\"\nif self.no_cache:\nreturn\nuid = self.report.generated_uid\ntry:\nreport = TestReport.get(uid)\nexcept InvalidArgumentError:\nreturn\nlogging.info(f\"Existing report {uid} was detected.\")\nlogging.info(\"The compatibilty test will not be re-executed.\")\nreturn report.results\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.execute","title":"execute()","text":"

Runs the test execution flow and returns the results

Returns:

Name Type Description dict

returns the results of the test execution.

Source code in cli/medperf/commands/compatibility_test/run.py
def execute(self):\n\"\"\"Runs the test execution flow and returns the results\n    Returns:\n        dict: returns the results of the test execution.\n    \"\"\"\nexecution_summary = Execution.run(\ndataset=self.dataset,\nmodel=self.model_cube,\nevaluator=self.evaluator_cube,\nignore_model_errors=False,\n)\nreturn execution_summary[\"results\"]\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.initialize_report","title":"initialize_report()","text":"

Initializes an instance of TestReport to hold the current test information.

Source code in cli/medperf/commands/compatibility_test/run.py
def initialize_report(self):\n\"\"\"Initializes an instance of `TestReport` to hold the current test information.\"\"\"\nreport_data = {\n\"demo_dataset_url\": self.demo_dataset_url,\n\"demo_dataset_hash\": self.demo_dataset_hash,\n\"data_path\": self.data_path,\n\"labels_path\": self.labels_path,\n\"prepared_data_hash\": self.data_uid,\n\"data_preparation_mlcube\": self.data_prep,\n\"model\": self.model,\n\"data_evaluator_mlcube\": self.evaluator,\n}\nself.report = TestReport(**report_data)\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.prepare_cubes","title":"prepare_cubes()","text":"

Prepares the mlcubes. If the provided mlcube is a path, it will create a temporary uid and link the cube path to the medperf storage path.

Source code in cli/medperf/commands/compatibility_test/run.py
def prepare_cubes(self):\n\"\"\"Prepares the mlcubes. If the provided mlcube is a path, it will create\n    a temporary uid and link the cube path to the medperf storage path.\"\"\"\nif self.data_source != \"prepared\":\nlogging.info(f\"Establishing the data preparation cube: {self.data_prep}\")\nself.data_prep = prepare_cube(self.data_prep)\nlogging.info(f\"Establishing the model cube: {self.model}\")\nself.model = prepare_cube(self.model)\nlogging.info(f\"Establishing the evaluator cube: {self.evaluator}\")\nself.evaluator = prepare_cube(self.evaluator)\nself.model_cube = get_cube(self.model, \"Model\", local_only=self.offline)\nself.evaluator_cube = get_cube(\nself.evaluator, \"Evaluator\", local_only=self.offline\n)\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.prepare_dataset","title":"prepare_dataset()","text":"

Assigns the data_uid used for testing and retrieves the dataset. If the data is not prepared, it calls the data preparation step on the given local data path or using a remote demo dataset.

Source code in cli/medperf/commands/compatibility_test/run.py
def prepare_dataset(self):\n\"\"\"Assigns the data_uid used for testing and retrieves the dataset.\n    If the data is not prepared, it calls the data preparation step\n    on the given local data path or using a remote demo dataset.\"\"\"\nlogging.info(\"Establishing data_uid for test execution\")\nif self.data_source != \"prepared\":\nif self.data_source == \"path\":\ndata_path, labels_path = self.data_path, self.labels_path\n# TODO: this has to be redesigned. Compatibility tests command\n#       is starting to have a lot of input arguments. For now\n#       let's not support accepting a metadata path\nmetadata_path = None\nelse:\ndata_path, labels_path, metadata_path = download_demo_data(\nself.demo_dataset_url, self.demo_dataset_hash\n)\nself.data_uid = create_test_dataset(\ndata_path,\nlabels_path,\nmetadata_path,\nself.data_prep,\nself.skip_data_preparation_step,\n)\nself.dataset = Dataset.get(self.data_uid, local_only=self.offline)\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.process_benchmark","title":"process_benchmark()","text":"

Process the benchmark input if given. Sets the needed parameters from the benchmark.

Source code in cli/medperf/commands/compatibility_test/run.py
def process_benchmark(self):\n\"\"\"Process the benchmark input if given. Sets the needed parameters from\n    the benchmark.\"\"\"\nif not self.benchmark_uid:\nreturn\nbenchmark = Benchmark.get(self.benchmark_uid, local_only=self.offline)\nif self.data_source != \"prepared\":\nself.data_prep = self.data_prep or benchmark.data_preparation_mlcube\nself.model = self.model or benchmark.reference_model_mlcube\nself.evaluator = self.evaluator or benchmark.data_evaluator_mlcube\nif self.data_source == \"benchmark\":\nself.demo_dataset_url = benchmark.demo_dataset_tarball_url\nself.demo_dataset_hash = benchmark.demo_dataset_tarball_hash\nself.skip_data_preparation_step = benchmark.metadata.get(\n\"demo_dataset_already_prepared\", False\n)\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.run","title":"run(benchmark=None, data_prep=None, model=None, evaluator=None, data_path=None, labels_path=None, demo_dataset_url=None, demo_dataset_hash=None, data_uid=None, no_cache=False, offline=False, skip_data_preparation_step=False) classmethod","text":"

Execute a test workflow. Components of a complete workflow should be passed. When only the benchmark is provided, it implies the following workflow will be used: - the benchmark's demo dataset is used as the raw data - the benchmark's data preparation cube is used - the benchmark's reference model cube is used - the benchmark's metrics cube is used

Overriding benchmark's components: - The data prepration, model, and metrics cubes can be overriden by specifying a cube either as an integer (registered) or a path (local). The path can refer either to the mlcube config file or to the mlcube directory containing the mlcube config file. - Instead of using the demo dataset of the benchmark, The input raw data can be overriden by providing: - a demo dataset url and its hash - data path and labels path - A prepared dataset can be directly used. In this case the data preparator cube is never used. The prepared data can be provided by either specifying an integer (registered) or a hash of a locally prepared dataset.

Whether the benchmark is provided or not, the command will fail either if the user fails to provide a valid complete workflow, or if the user provided extra redundant parameters.

Parameters:

Name Type Description Default benchmark int

Benchmark to run the test workflow for

None data_prep str

data preparation mlcube uid or local path.

None model str

model mlcube uid or local path.

None evaluator str

evaluator mlcube uid or local path.

None data_path str

path to a local raw data

None labels_path str

path to the labels of the local raw data

None demo_dataset_url str

Identifier to download the demonstration dataset tarball file.

None demo_dataset_hash str

The hash of the demo dataset tarball file

None data_uid str

A prepared dataset UID

None no_cache bool

Whether to ignore cached results of the test execution. Defaults to False.

False offline bool

Whether to disable communication to the MedPerf server and rely only on

False

Returns:

Type Description str

Prepared Dataset UID used for the test. Could be the one provided or a generated one.

dict

Results generated by the test.

Source code in cli/medperf/commands/compatibility_test/run.py
@classmethod\ndef run(\ncls,\nbenchmark: int = None,\ndata_prep: str = None,\nmodel: str = None,\nevaluator: str = None,\ndata_path: str = None,\nlabels_path: str = None,\ndemo_dataset_url: str = None,\ndemo_dataset_hash: str = None,\ndata_uid: str = None,\nno_cache: bool = False,\noffline: bool = False,\nskip_data_preparation_step: bool = False,\n) -> (str, dict):\n\"\"\"Execute a test workflow. Components of a complete workflow should be passed.\n    When only the benchmark is provided, it implies the following workflow will be used:\n    - the benchmark's demo dataset is used as the raw data\n    - the benchmark's data preparation cube is used\n    - the benchmark's reference model cube is used\n    - the benchmark's metrics cube is used\n    Overriding benchmark's components:\n    - The data prepration, model, and metrics cubes can be overriden by specifying a cube either\n    as an integer (registered) or a path (local). The path can refer either to the mlcube config\n    file or to the mlcube directory containing the mlcube config file.\n    - Instead of using the demo dataset of the benchmark, The input raw data can be overriden by providing:\n        - a demo dataset url and its hash\n        - data path and labels path\n    - A prepared dataset can be directly used. In this case the data preparator cube is never used.\n    The prepared data can be provided by either specifying an integer (registered) or a hash of a\n    locally prepared dataset.\n    Whether the benchmark is provided or not, the command will fail either if the user fails to\n    provide a valid complete workflow, or if the user provided extra redundant parameters.\n    Args:\n        benchmark (int, optional): Benchmark to run the test workflow for\n        data_prep (str, optional): data preparation mlcube uid or local path.\n        model (str, optional): model mlcube uid or local path.\n        evaluator (str, optional): evaluator mlcube uid or local path.\n        data_path (str, optional): path to a local raw data\n        labels_path (str, optional): path to the labels of the local raw data\n        demo_dataset_url (str, optional): Identifier to download the demonstration dataset tarball file.\\n\n        See `medperf mlcube submit --help` for more information\n        demo_dataset_hash (str, optional): The hash of the demo dataset tarball file\n        data_uid (str, optional): A prepared dataset UID\n        no_cache (bool): Whether to ignore cached results of the test execution. Defaults to False.\n        offline (bool): Whether to disable communication to the MedPerf server and rely only on\n        local copies of the server assets. Defaults to False.\n    Returns:\n        (str): Prepared Dataset UID used for the test. Could be the one provided or a generated one.\n        (dict): Results generated by the test.\n    \"\"\"\nlogging.info(\"Starting test execution\")\ntest_exec = cls(\nbenchmark,\ndata_prep,\nmodel,\nevaluator,\ndata_path,\nlabels_path,\ndemo_dataset_url,\ndemo_dataset_hash,\ndata_uid,\nno_cache,\noffline,\nskip_data_preparation_step,\n)\ntest_exec.validate()\ntest_exec.set_data_source()\ntest_exec.process_benchmark()\ntest_exec.prepare_cubes()\ntest_exec.prepare_dataset()\ntest_exec.initialize_report()\nresults = test_exec.cached_results()\nif results is None:\nresults = test_exec.execute()\ntest_exec.write(results)\nelse:\nlogging.info(\"Existing results are found. Test would not be re-executed.\")\nlogging.debug(f\"Existing results: {results}\")\nreturn test_exec.data_uid, results\n
"},{"location":"reference/commands/compatibility_test/run/#commands.compatibility_test.run.CompatibilityTestExecution.write","title":"write(results)","text":"

Writes a report of the test execution to the disk

Parameters:

Name Type Description Default results dict

the results of the test execution

required Source code in cli/medperf/commands/compatibility_test/run.py
def write(self, results):\n\"\"\"Writes a report of the test execution to the disk\n    Args:\n        results (dict): the results of the test execution\n    \"\"\"\nself.report.set_results(results)\nself.report.write()\n
"},{"location":"reference/commands/compatibility_test/utils/","title":"Utils","text":""},{"location":"reference/commands/compatibility_test/utils/#commands.compatibility_test.utils.download_demo_data","title":"download_demo_data(dset_url, dset_hash)","text":"

Retrieves the demo dataset associated to the specified benchmark

Returns:

Name Type Description data_path str

Location of the downloaded data

labels_path str

Location of the downloaded labels

Source code in cli/medperf/commands/compatibility_test/utils.py
def download_demo_data(dset_url, dset_hash):\n\"\"\"Retrieves the demo dataset associated to the specified benchmark\n    Returns:\n        data_path (str): Location of the downloaded data\n        labels_path (str): Location of the downloaded labels\n    \"\"\"\ntry:\ndemo_dset_path, _ = resources.get_benchmark_demo_dataset(dset_url, dset_hash)\nexcept InvalidEntityError as e:\nraise InvalidEntityError(f\"Demo dataset {dset_url}: {e}\")\n# It is assumed that all demo datasets contain a file\n# which specifies the input of the data preparation step\npaths_file = os.path.join(demo_dset_path, config.demo_dset_paths_file)\nwith open(paths_file, \"r\") as f:\npaths = yaml.safe_load(f)\ndata_path = os.path.join(demo_dset_path, paths[\"data_path\"])\nlabels_path = os.path.join(demo_dset_path, paths[\"labels_path\"])\nmetadata_path = None\nif \"metadata_path\" in paths:\nmetadata_path = os.path.join(demo_dset_path, paths[\"metadata_path\"])\nreturn data_path, labels_path, metadata_path\n
"},{"location":"reference/commands/compatibility_test/utils/#commands.compatibility_test.utils.prepare_cube","title":"prepare_cube(cube_uid)","text":"

Assigns the attr used for testing according to the initialization parameters. If the value is a path, it will create a temporary uid and link the cube path to the medperf storage path.

Parameters:

Name Type Description Default attr str

Attribute to check and/or reassign.

required fallback any

Value to assign if attribute is empty. Defaults to None.

required Source code in cli/medperf/commands/compatibility_test/utils.py
def prepare_cube(cube_uid: str):\n\"\"\"Assigns the attr used for testing according to the initialization parameters.\n    If the value is a path, it will create a temporary uid and link the cube path to\n    the medperf storage path.\n    Arguments:\n        attr (str): Attribute to check and/or reassign.\n        fallback (any): Value to assign if attribute is empty. Defaults to None.\n    \"\"\"\n# Test if value looks like an mlcube_uid, if so skip path validation\nif str(cube_uid).isdigit():\nlogging.info(f\"MLCube value {cube_uid} resembles an mlcube_uid\")\nreturn cube_uid\n# Check if value is a local mlcube\npath = Path(cube_uid)\nif path.is_file():\npath = path.parent\npath = path.resolve()\nif os.path.exists(path):\nmlcube_yaml_path = os.path.join(path, config.cube_filename)\nif os.path.exists(mlcube_yaml_path):\nlogging.info(\"local path provided. Creating symbolic link\")\ntemp_uid = prepare_local_cube(path)\nreturn temp_uid\nlogging.error(f\"mlcube {cube_uid} was not found as an existing mlcube\")\nraise InvalidArgumentError(\nf\"The provided mlcube ({cube_uid}) could not be found as a local or remote mlcube\"\n)\n
"},{"location":"reference/commands/compatibility_test/validate_params/","title":"Validate params","text":""},{"location":"reference/commands/compatibility_test/validate_params/#commands.compatibility_test.validate_params.CompatibilityTestParamsValidator","title":"CompatibilityTestParamsValidator","text":"

Validates the input parameters to the CompatibilityTestExecution class

Source code in cli/medperf/commands/compatibility_test/validate_params.py
class CompatibilityTestParamsValidator:\n\"\"\"Validates the input parameters to the CompatibilityTestExecution class\"\"\"\ndef __init__(\nself,\nbenchmark: int = None,\ndata_prep: str = None,\nmodel: str = None,\nevaluator: str = None,\ndata_path: str = None,\nlabels_path: str = None,\ndemo_dataset_url: str = None,\ndemo_dataset_hash: str = None,\ndata_uid: str = None,\n):\nself.benchmark_uid = benchmark\nself.data_prep = data_prep\nself.model = model\nself.evaluator = evaluator\nself.data_path = data_path\nself.labels_path = labels_path\nself.demo_dataset_url = demo_dataset_url\nself.demo_dataset_hash = demo_dataset_hash\nself.data_uid = data_uid\ndef __validate_cubes(self):\nif not self.model and not self.benchmark_uid:\nraise InvalidArgumentError(\n\"A model mlcube or a benchmark should at least be specified\"\n)\nif not self.evaluator and not self.benchmark_uid:\nraise InvalidArgumentError(\n\"A metrics mlcube or a benchmark should at least be specified\"\n)\ndef __raise_redundant_data_source(self):\nmsg = \"Make sure you pass only one data source: \"\nmsg += \"either a prepared dataset, a data path and labels path, or a demo dataset url\"\nraise InvalidArgumentError(msg)\ndef __validate_prepared_data_source(self):\nif any(\n[\nself.data_path,\nself.labels_path,\nself.demo_dataset_url,\nself.demo_dataset_hash,\n]\n):\nself.__raise_redundant_data_source()\nif self.data_prep:\nraise InvalidArgumentError(\n\"A data preparation cube is not needed when specifying a prepared dataset\"\n)\ndef __validate_data_path_source(self):\nif not self.labels_path:\nraise InvalidArgumentError(\n\"Labels path should be specified when providing data path\"\n)\nif any([self.demo_dataset_url, self.demo_dataset_hash, self.data_uid]):\nself.__raise_redundant_data_source()\nif not self.data_prep and not self.benchmark_uid:\nraise InvalidArgumentError(\n\"A data preparation cube should be passed when specifying raw data input\"\n)\ndef __validate_demo_data_source(self):\nif not self.demo_dataset_hash:\nraise InvalidArgumentError(\n\"The hash of the provided demo dataset should be specified\"\n)\nif any([self.data_path, self.labels_path, self.data_uid]):\nself.__raise_redundant_data_source()\nif not self.data_prep and not self.benchmark_uid:\nraise InvalidArgumentError(\n\"A data preparation cube should be passed when specifying raw data input\"\n)\ndef __validate_data_source(self):\nif self.data_uid:\nself.__validate_prepared_data_source()\nreturn\nif self.data_path:\nself.__validate_data_path_source()\nreturn\nif self.demo_dataset_url:\nself.__validate_demo_data_source()\nreturn\nif self.benchmark_uid:\nreturn\nmsg = \"A data source should at least be specified, either by providing\"\nmsg += \" a prepared data uid, a demo dataset url, data path, or a benchmark\"\nraise InvalidArgumentError(msg)\ndef __validate_redundant_benchmark(self):\nif not self.benchmark_uid:\nreturn\nredundant_bmk_demo = any([self.data_uid, self.data_path, self.demo_dataset_url])\nredundant_bmk_model = self.model is not None\nredundant_bmk_evaluator = self.evaluator is not None\nredundant_bmk_preparator = (\nself.data_prep is not None or self.data_uid is not None\n)\nif all(\n[\nredundant_bmk_demo,\nredundant_bmk_model,\nredundant_bmk_evaluator,\nredundant_bmk_preparator,\n]\n):\nraise InvalidArgumentError(\"The provided benchmark will not be used\")\ndef validate(self):\n\"\"\"Ensures test has been passed a valid combination of parameters.\n        Raises `medperf.exceptions.InvalidArgumentError` when the parameters are\n        invalid.\n        \"\"\"\nself.__validate_cubes()\nself.__validate_data_source()\nself.__validate_redundant_benchmark()\ndef get_data_source(self):\n\"\"\"Parses the input parameters and returns a string, one of:\n        \"prepared\", if the source of data is a prepared dataset uid,\n        \"path\", if the source of data is a local path to raw data,\n        \"demo\", if the source of data is a demo dataset url,\n        or \"benchmark\", if the source of data is the demo dataset of a benchmark.\n        This function assumes the passed parameters to the constructor have been already\n        validated.\n        \"\"\"\nif self.data_uid:\nreturn \"prepared\"\nif self.data_path:\nreturn \"path\"\nif self.demo_dataset_url:\nreturn \"demo\"\nif self.benchmark_uid:\nreturn \"benchmark\"\nraise MedperfException(\n\"Ensure calling the `validate` method before using this method\"\n)\n
"},{"location":"reference/commands/compatibility_test/validate_params/#commands.compatibility_test.validate_params.CompatibilityTestParamsValidator.get_data_source","title":"get_data_source()","text":"

Parses the input parameters and returns a string, one of: \"prepared\", if the source of data is a prepared dataset uid, \"path\", if the source of data is a local path to raw data, \"demo\", if the source of data is a demo dataset url, or \"benchmark\", if the source of data is the demo dataset of a benchmark.

This function assumes the passed parameters to the constructor have been already validated.

Source code in cli/medperf/commands/compatibility_test/validate_params.py
def get_data_source(self):\n\"\"\"Parses the input parameters and returns a string, one of:\n    \"prepared\", if the source of data is a prepared dataset uid,\n    \"path\", if the source of data is a local path to raw data,\n    \"demo\", if the source of data is a demo dataset url,\n    or \"benchmark\", if the source of data is the demo dataset of a benchmark.\n    This function assumes the passed parameters to the constructor have been already\n    validated.\n    \"\"\"\nif self.data_uid:\nreturn \"prepared\"\nif self.data_path:\nreturn \"path\"\nif self.demo_dataset_url:\nreturn \"demo\"\nif self.benchmark_uid:\nreturn \"benchmark\"\nraise MedperfException(\n\"Ensure calling the `validate` method before using this method\"\n)\n
"},{"location":"reference/commands/compatibility_test/validate_params/#commands.compatibility_test.validate_params.CompatibilityTestParamsValidator.validate","title":"validate()","text":"

Ensures test has been passed a valid combination of parameters. Raises medperf.exceptions.InvalidArgumentError when the parameters are invalid.

Source code in cli/medperf/commands/compatibility_test/validate_params.py
def validate(self):\n\"\"\"Ensures test has been passed a valid combination of parameters.\n    Raises `medperf.exceptions.InvalidArgumentError` when the parameters are\n    invalid.\n    \"\"\"\nself.__validate_cubes()\nself.__validate_data_source()\nself.__validate_redundant_benchmark()\n
"},{"location":"reference/commands/dataset/associate/","title":"Associate","text":""},{"location":"reference/commands/dataset/associate/#commands.dataset.associate.AssociateDataset","title":"AssociateDataset","text":"Source code in cli/medperf/commands/dataset/associate.py
class AssociateDataset:\n@staticmethod\ndef run(data_uid: int, benchmark_uid: int, approved=False, no_cache=False):\n\"\"\"Associates a registered dataset with a benchmark\n        Args:\n            data_uid (int): UID of the registered dataset to associate\n            benchmark_uid (int): UID of the benchmark to associate with\n        \"\"\"\ncomms = config.comms\nui = config.ui\ndset = Dataset.get(data_uid)\nif dset.id is None:\nmsg = \"The provided dataset is not registered.\"\nraise InvalidArgumentError(msg)\nbenchmark = Benchmark.get(benchmark_uid)\nif dset.data_preparation_mlcube != benchmark.data_preparation_mlcube:\nraise InvalidArgumentError(\n\"The specified dataset wasn't prepared for this benchmark\"\n)\nresult = BenchmarkExecution.run(\nbenchmark_uid,\ndata_uid,\n[benchmark.reference_model_mlcube],\nno_cache=no_cache,\n)[0]\nui.print(\"These are the results generated by the compatibility test. \")\nui.print(\"This will be sent along the association request.\")\nui.print(\"They will not be part of the benchmark.\")\ndict_pretty_print(result.results)\nmsg = \"Please confirm that you would like to associate\"\nmsg += f\" the dataset {dset.name} with the benchmark {benchmark.name}.\"\nmsg += \" [Y/n]\"\napproved = approved or approval_prompt(msg)\nif approved:\nui.print(\"Generating dataset benchmark association\")\nmetadata = {\"test_result\": result.results}\ncomms.associate_dset(dset.id, benchmark_uid, metadata)\nelse:\nui.print(\"Dataset association operation cancelled.\")\n
"},{"location":"reference/commands/dataset/associate/#commands.dataset.associate.AssociateDataset.run","title":"run(data_uid, benchmark_uid, approved=False, no_cache=False) staticmethod","text":"

Associates a registered dataset with a benchmark

Parameters:

Name Type Description Default data_uid int

UID of the registered dataset to associate

required benchmark_uid int

UID of the benchmark to associate with

required Source code in cli/medperf/commands/dataset/associate.py
@staticmethod\ndef run(data_uid: int, benchmark_uid: int, approved=False, no_cache=False):\n\"\"\"Associates a registered dataset with a benchmark\n    Args:\n        data_uid (int): UID of the registered dataset to associate\n        benchmark_uid (int): UID of the benchmark to associate with\n    \"\"\"\ncomms = config.comms\nui = config.ui\ndset = Dataset.get(data_uid)\nif dset.id is None:\nmsg = \"The provided dataset is not registered.\"\nraise InvalidArgumentError(msg)\nbenchmark = Benchmark.get(benchmark_uid)\nif dset.data_preparation_mlcube != benchmark.data_preparation_mlcube:\nraise InvalidArgumentError(\n\"The specified dataset wasn't prepared for this benchmark\"\n)\nresult = BenchmarkExecution.run(\nbenchmark_uid,\ndata_uid,\n[benchmark.reference_model_mlcube],\nno_cache=no_cache,\n)[0]\nui.print(\"These are the results generated by the compatibility test. \")\nui.print(\"This will be sent along the association request.\")\nui.print(\"They will not be part of the benchmark.\")\ndict_pretty_print(result.results)\nmsg = \"Please confirm that you would like to associate\"\nmsg += f\" the dataset {dset.name} with the benchmark {benchmark.name}.\"\nmsg += \" [Y/n]\"\napproved = approved or approval_prompt(msg)\nif approved:\nui.print(\"Generating dataset benchmark association\")\nmetadata = {\"test_result\": result.results}\ncomms.associate_dset(dset.id, benchmark_uid, metadata)\nelse:\nui.print(\"Dataset association operation cancelled.\")\n
"},{"location":"reference/commands/dataset/dataset/","title":"Dataset","text":""},{"location":"reference/commands/dataset/dataset/#commands.dataset.dataset.associate","title":"associate(data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), benchmark_uid=typer.Option(..., '--benchmark_uid', '-b', help='Benchmark UID'), approval=typer.Option(False, '-y', help='Skip approval step'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist'))","text":"

Associate a registered dataset with a specific benchmark. The dataset and benchmark must share the same data preparation cube.

Source code in cli/medperf/commands/dataset/dataset.py
@app.command(\"associate\")\n@clean_except\ndef associate(\ndata_uid: int = typer.Option(\n..., \"--data_uid\", \"-d\", help=\"Registered Dataset UID\"\n),\nbenchmark_uid: int = typer.Option(\n..., \"--benchmark_uid\", \"-b\", help=\"Benchmark UID\"\n),\napproval: bool = typer.Option(False, \"-y\", help=\"Skip approval step\"),\nno_cache: bool = typer.Option(\nFalse,\n\"--no-cache\",\nhelp=\"Execute the test even if results already exist\",\n),\n):\n\"\"\"Associate a registered dataset with a specific benchmark.\n    The dataset and benchmark must share the same data preparation cube.\n    \"\"\"\nui = config.ui\nAssociateDataset.run(data_uid, benchmark_uid, approved=approval, no_cache=no_cache)\nui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/dataset/dataset/#commands.dataset.dataset.list","title":"list(local=typer.Option(False, '--local', help='Get local datasets'), mine=typer.Option(False, '--mine', help='Get current-user datasets'), mlcube=typer.Option(None, '--mlcube', '-m', help='Get datasets for a given data prep mlcube'))","text":"

List datasets stored locally and remotely from the user

Source code in cli/medperf/commands/dataset/dataset.py
@app.command(\"ls\")\n@clean_except\ndef list(\nlocal: bool = typer.Option(False, \"--local\", help=\"Get local datasets\"),\nmine: bool = typer.Option(False, \"--mine\", help=\"Get current-user datasets\"),\nmlcube: int = typer.Option(\nNone, \"--mlcube\", \"-m\", help=\"Get datasets for a given data prep mlcube\"\n),\n):\n\"\"\"List datasets stored locally and remotely from the user\"\"\"\nEntityList.run(\nDataset,\nfields=[\"UID\", \"Name\", \"Data Preparation Cube UID\", \"State\", \"Status\", \"Owner\"],\nlocal_only=local,\nmine_only=mine,\nmlcube=mlcube,\n)\n
"},{"location":"reference/commands/dataset/dataset/#commands.dataset.dataset.prepare","title":"prepare(data_uid=typer.Option(..., '--data_uid', '-d', help='Dataset UID'), approval=typer.Option(False, '-y', help='Skip report submission approval step (In this case, it is assumed to be approved)'))","text":"

Runs the Data preparation step for a raw dataset

Source code in cli/medperf/commands/dataset/dataset.py
@app.command(\"prepare\")\n@clean_except\ndef prepare(\ndata_uid: str = typer.Option(..., \"--data_uid\", \"-d\", help=\"Dataset UID\"),\napproval: bool = typer.Option(\nFalse,\n\"-y\",\nhelp=\"Skip report submission approval step (In this case, it is assumed to be approved)\",\n),\n):\n\"\"\"Runs the Data preparation step for a raw dataset\"\"\"\nui = config.ui\nDataPreparation.run(data_uid, approve_sending_reports=approval)\nui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/dataset/dataset/#commands.dataset.dataset.set_operational","title":"set_operational(data_uid=typer.Option(..., '--data_uid', '-d', help='Dataset UID'), approval=typer.Option(False, '-y', help='Skip confirmation and statistics submission approval step'))","text":"

Marks a dataset as Operational

Source code in cli/medperf/commands/dataset/dataset.py
@app.command(\"set_operational\")\n@clean_except\ndef set_operational(\ndata_uid: str = typer.Option(..., \"--data_uid\", \"-d\", help=\"Dataset UID\"),\napproval: bool = typer.Option(\nFalse, \"-y\", help=\"Skip confirmation and statistics submission approval step\"\n),\n):\n\"\"\"Marks a dataset as Operational\"\"\"\nui = config.ui\nDatasetSetOperational.run(data_uid, approved=approval)\nui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/dataset/dataset/#commands.dataset.dataset.submit","title":"submit(benchmark_uid=typer.Option(None, '--benchmark', '-b', help='UID of the desired benchmark'), data_prep_uid=typer.Option(None, '--data_prep', '-p', help='UID of the desired preparation cube'), data_path=typer.Option(..., '--data_path', '-d', help='Path to the data'), labels_path=typer.Option(..., '--labels_path', '-l', help='Path to the labels'), metadata_path=typer.Option(None, '--metadata_path', '-m', help='Metadata folder location (Might be required if the dataset is already prepared)'), name=typer.Option(..., '--name', help='A human-readable name of the dataset'), description=typer.Option(None, '--description', help='A description of the dataset'), location=typer.Option(None, '--location', help='Location or Institution the data belongs to'), approval=typer.Option(False, '-y', help='Skip approval step'), submit_as_prepared=typer.Option(False, '--submit-as-prepared', help='Use this flag if the dataset is already prepared'))","text":"

Submits a Dataset instance to the backend

Source code in cli/medperf/commands/dataset/dataset.py
@app.command(\"submit\")\n@clean_except\ndef submit(\nbenchmark_uid: int = typer.Option(\nNone, \"--benchmark\", \"-b\", help=\"UID of the desired benchmark\"\n),\ndata_prep_uid: int = typer.Option(\nNone, \"--data_prep\", \"-p\", help=\"UID of the desired preparation cube\"\n),\ndata_path: str = typer.Option(..., \"--data_path\", \"-d\", help=\"Path to the data\"),\nlabels_path: str = typer.Option(\n..., \"--labels_path\", \"-l\", help=\"Path to the labels\"\n),\nmetadata_path: str = typer.Option(\nNone,\n\"--metadata_path\",\n\"-m\",\nhelp=\"Metadata folder location (Might be required if the dataset is already prepared)\",\n),\nname: str = typer.Option(\n..., \"--name\", help=\"A human-readable name of the dataset\"\n),\ndescription: str = typer.Option(\nNone, \"--description\", help=\"A description of the dataset\"\n),\nlocation: str = typer.Option(\nNone, \"--location\", help=\"Location or Institution the data belongs to\"\n),\napproval: bool = typer.Option(False, \"-y\", help=\"Skip approval step\"),\nsubmit_as_prepared: bool = typer.Option(\nFalse,\n\"--submit-as-prepared\",\nhelp=\"Use this flag if the dataset is already prepared\",\n),\n):\n\"\"\"Submits a Dataset instance to the backend\"\"\"\nui = config.ui\nDataCreation.run(\nbenchmark_uid,\ndata_prep_uid,\ndata_path,\nlabels_path,\nmetadata_path,\nname=name,\ndescription=description,\nlocation=location,\napproved=approval,\nsubmit_as_prepared=submit_as_prepared,\n)\nui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/dataset/dataset/#commands.dataset.dataset.view","title":"view(entity_id=typer.Argument(None, help='Dataset ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local datasets if dataset ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user datasets if dataset ID is not provided'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed'))","text":"

Displays the information of one or more datasets

Source code in cli/medperf/commands/dataset/dataset.py
@app.command(\"view\")\n@clean_except\ndef view(\nentity_id: Optional[str] = typer.Argument(None, help=\"Dataset ID\"),\nformat: str = typer.Option(\n\"yaml\",\n\"-f\",\n\"--format\",\nhelp=\"Format to display contents. Available formats: [yaml, json]\",\n),\nlocal: bool = typer.Option(\nFalse, \"--local\", help=\"Display local datasets if dataset ID is not provided\"\n),\nmine: bool = typer.Option(\nFalse,\n\"--mine\",\nhelp=\"Display current-user datasets if dataset ID is not provided\",\n),\noutput: str = typer.Option(\nNone,\n\"--output\",\n\"-o\",\nhelp=\"Output file to store contents. If not provided, the output will be displayed\",\n),\n):\n\"\"\"Displays the information of one or more datasets\"\"\"\nEntityView.run(entity_id, Dataset, format, local, mine, output)\n
"},{"location":"reference/commands/dataset/prepare/","title":"Prepare","text":""},{"location":"reference/commands/dataset/set_operational/","title":"Set operational","text":""},{"location":"reference/commands/dataset/set_operational/#commands.dataset.set_operational.DatasetSetOperational","title":"DatasetSetOperational","text":"Source code in cli/medperf/commands/dataset/set_operational.py
class DatasetSetOperational:\n# TODO: this will be refactored when merging entity edit PR\n@classmethod\ndef run(cls, dataset_id: int, approved: bool = False):\npreparation = cls(dataset_id, approved)\npreparation.validate()\npreparation.generate_uids()\npreparation.set_statistics()\npreparation.set_operational()\npreparation.update()\npreparation.write()\nreturn preparation.dataset.id\ndef __init__(self, dataset_id: int, approved: bool):\nself.ui = config.ui\nself.dataset = Dataset.get(dataset_id)\nself.approved = approved\ndef validate(self):\nif self.dataset.state == \"OPERATION\":\nraise InvalidArgumentError(\"The dataset is already operational\")\nif not self.dataset.is_ready():\nraise InvalidArgumentError(\"The dataset is not checked\")\ndef generate_uids(self):\n\"\"\"Auto-generates dataset UIDs for both input and output paths\"\"\"\nraw_data_path, raw_labels_path = self.dataset.get_raw_paths()\nprepared_data_path = self.dataset.data_path\nprepared_labels_path = self.dataset.labels_path\nin_uid = get_folders_hash([raw_data_path, raw_labels_path])\ngenerated_uid = get_folders_hash([prepared_data_path, prepared_labels_path])\nself.dataset.input_data_hash = in_uid\nself.dataset.generated_uid = generated_uid\ndef set_statistics(self):\nwith open(self.dataset.statistics_path, \"r\") as f:\nstats = yaml.safe_load(f)\nself.dataset.generated_metadata = stats\ndef set_operational(self):\nself.dataset.state = \"OPERATION\"\ndef update(self):\nbody = self.todict()\ndict_pretty_print(body)\nmsg = \"Do you approve sending the presented data to MedPerf? [Y/n] \"\nself.approved = self.approved or approval_prompt(msg)\nif self.approved:\nconfig.comms.update_dataset(self.dataset.id, body)\nreturn\nraise CleanExit(\"Setting Dataset as operational was cancelled\")\ndef todict(self) -> dict:\n\"\"\"Dictionary representation of the update body\n        Returns:\n            dict: dictionary containing information pertaining the dataset.\n        \"\"\"\nreturn {\n\"input_data_hash\": self.dataset.input_data_hash,\n\"generated_uid\": self.dataset.generated_uid,\n\"generated_metadata\": self.dataset.generated_metadata,\n\"state\": self.dataset.state,\n}\ndef write(self) -> str:\n\"\"\"Writes the registration into disk\n        Args:\n            filename (str, optional): name of the file. Defaults to config.reg_file.\n        \"\"\"\nself.dataset.write()\n
"},{"location":"reference/commands/dataset/set_operational/#commands.dataset.set_operational.DatasetSetOperational.generate_uids","title":"generate_uids()","text":"

Auto-generates dataset UIDs for both input and output paths

Source code in cli/medperf/commands/dataset/set_operational.py
def generate_uids(self):\n\"\"\"Auto-generates dataset UIDs for both input and output paths\"\"\"\nraw_data_path, raw_labels_path = self.dataset.get_raw_paths()\nprepared_data_path = self.dataset.data_path\nprepared_labels_path = self.dataset.labels_path\nin_uid = get_folders_hash([raw_data_path, raw_labels_path])\ngenerated_uid = get_folders_hash([prepared_data_path, prepared_labels_path])\nself.dataset.input_data_hash = in_uid\nself.dataset.generated_uid = generated_uid\n
"},{"location":"reference/commands/dataset/set_operational/#commands.dataset.set_operational.DatasetSetOperational.todict","title":"todict()","text":"

Dictionary representation of the update body

Returns:

Name Type Description dict dict

dictionary containing information pertaining the dataset.

Source code in cli/medperf/commands/dataset/set_operational.py
def todict(self) -> dict:\n\"\"\"Dictionary representation of the update body\n    Returns:\n        dict: dictionary containing information pertaining the dataset.\n    \"\"\"\nreturn {\n\"input_data_hash\": self.dataset.input_data_hash,\n\"generated_uid\": self.dataset.generated_uid,\n\"generated_metadata\": self.dataset.generated_metadata,\n\"state\": self.dataset.state,\n}\n
"},{"location":"reference/commands/dataset/set_operational/#commands.dataset.set_operational.DatasetSetOperational.write","title":"write()","text":"

Writes the registration into disk

Parameters:

Name Type Description Default filename str

name of the file. Defaults to config.reg_file.

required Source code in cli/medperf/commands/dataset/set_operational.py
def write(self) -> str:\n\"\"\"Writes the registration into disk\n    Args:\n        filename (str, optional): name of the file. Defaults to config.reg_file.\n    \"\"\"\nself.dataset.write()\n
"},{"location":"reference/commands/dataset/submit/","title":"Submit","text":""},{"location":"reference/commands/dataset/submit/#commands.dataset.submit.DataCreation","title":"DataCreation","text":"Source code in cli/medperf/commands/dataset/submit.py
class DataCreation:\n@classmethod\ndef run(\ncls,\nbenchmark_uid: int,\nprep_cube_uid: int,\ndata_path: str,\nlabels_path: str,\nmetadata_path: str = None,\nname: str = None,\ndescription: str = None,\nlocation: str = None,\napproved: bool = False,\nsubmit_as_prepared: bool = False,\nfor_test: bool = False,\n):\npreparation = cls(\nbenchmark_uid,\nprep_cube_uid,\ndata_path,\nlabels_path,\nmetadata_path,\nname,\ndescription,\nlocation,\napproved,\nsubmit_as_prepared,\nfor_test,\n)\npreparation.validate()\npreparation.validate_prep_cube()\npreparation.create_dataset_object()\nif submit_as_prepared:\npreparation.make_dataset_prepared()\nupdated_dataset_dict = preparation.upload()\npreparation.to_permanent_path(updated_dataset_dict)\npreparation.write(updated_dataset_dict)\nreturn updated_dataset_dict[\"id\"]\ndef __init__(\nself,\nbenchmark_uid: int,\nprep_cube_uid: int,\ndata_path: str,\nlabels_path: str,\nmetadata_path: str,\nname: str,\ndescription: str,\nlocation: str,\napproved: bool,\nsubmit_as_prepared: bool,\nfor_test: bool,\n):\nself.ui = config.ui\nself.data_path = str(Path(data_path).resolve())\nself.labels_path = str(Path(labels_path).resolve())\nself.metadata_path = metadata_path\nself.name = name\nself.description = description\nself.location = location\nself.benchmark_uid = benchmark_uid\nself.prep_cube_uid = prep_cube_uid\nself.approved = approved\nself.submit_as_prepared = submit_as_prepared\nself.for_test = for_test\ndef validate(self):\nif not os.path.exists(self.data_path):\nraise InvalidArgumentError(\"The provided data path doesn't exist\")\nif not os.path.exists(self.labels_path):\nraise InvalidArgumentError(\"The provided labels path doesn't exist\")\nif not self.submit_as_prepared and self.metadata_path:\nraise InvalidArgumentError(\n\"metadata path should only be provided when the dataset is submitted as prepared\"\n)\nif self.metadata_path:\nself.metadata_path = str(Path(self.metadata_path).resolve())\nif not os.path.exists(self.metadata_path):\nraise InvalidArgumentError(\"The provided metadata path doesn't exist\")\n# TODO: should we check the prep mlcube and accordingly check if metadata path\n#       is required? For now, we will anyway create an empty metadata folder\n#       (in self.make_dataset_prepared)\ntoo_many_resources = self.benchmark_uid and self.prep_cube_uid\nno_resource = self.benchmark_uid is None and self.prep_cube_uid is None\nif no_resource or too_many_resources:\nraise InvalidArgumentError(\n\"Must provide either a benchmark or a preparation mlcube\"\n)\ndef validate_prep_cube(self):\nif self.prep_cube_uid is None:\nbenchmark = Benchmark.get(self.benchmark_uid)\nself.prep_cube_uid = benchmark.data_preparation_mlcube\nCube.get(self.prep_cube_uid)\ndef create_dataset_object(self):\n\"\"\"generates dataset UIDs for both input path\"\"\"\nin_uid = get_folders_hash([self.data_path, self.labels_path])\ndataset = Dataset(\nname=self.name,\ndescription=self.description,\nlocation=self.location,\ndata_preparation_mlcube=self.prep_cube_uid,\ninput_data_hash=in_uid,\ngenerated_uid=in_uid,\nsplit_seed=0,\ngenerated_metadata={},\nstate=\"DEVELOPMENT\",\nsubmitted_as_prepared=self.submit_as_prepared,\nfor_test=self.for_test,\n)\ndataset.write()\nconfig.tmp_paths.append(dataset.path)\ndataset.set_raw_paths(\nraw_data_path=self.data_path,\nraw_labels_path=self.labels_path,\n)\nself.dataset = dataset\ndef make_dataset_prepared(self):\nshutil.copytree(self.data_path, self.dataset.data_path)\nshutil.copytree(self.labels_path, self.dataset.labels_path)\nif self.metadata_path:\nshutil.copytree(self.metadata_path, self.dataset.metadata_path)\nelse:\n# Create an empty folder. The statistics logic should\n# also expect an empty folder to accommodate for users who\n# have prepared datasets with no the metadata information\nos.makedirs(self.dataset.metadata_path, exist_ok=True)\ndef upload(self):\nsubmission_dict = self.dataset.todict()\ndict_pretty_print(submission_dict)\nmsg = \"Do you approve the registration of the presented data to MedPerf? [Y/n] \"\nself.approved = self.approved or approval_prompt(msg)\nif self.approved:\nupdated_body = self.dataset.upload()\nreturn updated_body\nraise CleanExit(\"Dataset submission operation cancelled\")\ndef to_permanent_path(self, updated_dataset_dict: dict):\n\"\"\"Renames the temporary benchmark submission to a permanent one\n        Args:\n            bmk_dict (dict): dictionary containing updated information of the submitted benchmark\n        \"\"\"\nold_dataset_loc = self.dataset.path\nupdated_dataset = Dataset(**updated_dataset_dict)\nnew_dataset_loc = updated_dataset.path\nremove_path(new_dataset_loc)\nos.rename(old_dataset_loc, new_dataset_loc)\ndef write(self, updated_dataset_dict):\ndataset = Dataset(**updated_dataset_dict)\ndataset.write()\n
"},{"location":"reference/commands/dataset/submit/#commands.dataset.submit.DataCreation.create_dataset_object","title":"create_dataset_object()","text":"

generates dataset UIDs for both input path

Source code in cli/medperf/commands/dataset/submit.py
def create_dataset_object(self):\n\"\"\"generates dataset UIDs for both input path\"\"\"\nin_uid = get_folders_hash([self.data_path, self.labels_path])\ndataset = Dataset(\nname=self.name,\ndescription=self.description,\nlocation=self.location,\ndata_preparation_mlcube=self.prep_cube_uid,\ninput_data_hash=in_uid,\ngenerated_uid=in_uid,\nsplit_seed=0,\ngenerated_metadata={},\nstate=\"DEVELOPMENT\",\nsubmitted_as_prepared=self.submit_as_prepared,\nfor_test=self.for_test,\n)\ndataset.write()\nconfig.tmp_paths.append(dataset.path)\ndataset.set_raw_paths(\nraw_data_path=self.data_path,\nraw_labels_path=self.labels_path,\n)\nself.dataset = dataset\n
"},{"location":"reference/commands/dataset/submit/#commands.dataset.submit.DataCreation.to_permanent_path","title":"to_permanent_path(updated_dataset_dict)","text":"

Renames the temporary benchmark submission to a permanent one

Parameters:

Name Type Description Default bmk_dict dict

dictionary containing updated information of the submitted benchmark

required Source code in cli/medperf/commands/dataset/submit.py
def to_permanent_path(self, updated_dataset_dict: dict):\n\"\"\"Renames the temporary benchmark submission to a permanent one\n    Args:\n        bmk_dict (dict): dictionary containing updated information of the submitted benchmark\n    \"\"\"\nold_dataset_loc = self.dataset.path\nupdated_dataset = Dataset(**updated_dataset_dict)\nnew_dataset_loc = updated_dataset.path\nremove_path(new_dataset_loc)\nos.rename(old_dataset_loc, new_dataset_loc)\n
"},{"location":"reference/commands/mlcube/associate/","title":"Associate","text":""},{"location":"reference/commands/mlcube/associate/#commands.mlcube.associate.AssociateCube","title":"AssociateCube","text":"Source code in cli/medperf/commands/mlcube/associate.py
class AssociateCube:\n@classmethod\ndef run(\ncls,\ncube_uid: int,\nbenchmark_uid: int,\napproved=False,\nno_cache=False,\n):\n\"\"\"Associates a cube with a given benchmark\n        Args:\n            cube_uid (int): UID of model MLCube\n            benchmark_uid (int): UID of benchmark\n            approved (bool): Skip validation step. Defualts to False\n        \"\"\"\ncomms = config.comms\nui = config.ui\ncube = Cube.get(cube_uid)\nbenchmark = Benchmark.get(benchmark_uid)\n_, results = CompatibilityTestExecution.run(\nbenchmark=benchmark_uid, model=cube_uid, no_cache=no_cache\n)\nui.print(\"These are the results generated by the compatibility test. \")\nui.print(\"This will be sent along the association request.\")\nui.print(\"They will not be part of the benchmark.\")\ndict_pretty_print(results)\nmsg = \"Please confirm that you would like to associate \"\nmsg += f\"the MLCube '{cube.name}' with the benchmark '{benchmark.name}' [Y/n]\"\napproved = approved or approval_prompt(msg)\nif approved:\nui.print(\"Generating mlcube benchmark association\")\nmetadata = {\"test_result\": results}\ncomms.associate_cube(cube_uid, benchmark_uid, metadata)\nelse:\nui.print(\"MLCube association operation cancelled\")\n
"},{"location":"reference/commands/mlcube/associate/#commands.mlcube.associate.AssociateCube.run","title":"run(cube_uid, benchmark_uid, approved=False, no_cache=False) classmethod","text":"

Associates a cube with a given benchmark

Parameters:

Name Type Description Default cube_uid int

UID of model MLCube

required benchmark_uid int

UID of benchmark

required approved bool

Skip validation step. Defualts to False

False Source code in cli/medperf/commands/mlcube/associate.py
@classmethod\ndef run(\ncls,\ncube_uid: int,\nbenchmark_uid: int,\napproved=False,\nno_cache=False,\n):\n\"\"\"Associates a cube with a given benchmark\n    Args:\n        cube_uid (int): UID of model MLCube\n        benchmark_uid (int): UID of benchmark\n        approved (bool): Skip validation step. Defualts to False\n    \"\"\"\ncomms = config.comms\nui = config.ui\ncube = Cube.get(cube_uid)\nbenchmark = Benchmark.get(benchmark_uid)\n_, results = CompatibilityTestExecution.run(\nbenchmark=benchmark_uid, model=cube_uid, no_cache=no_cache\n)\nui.print(\"These are the results generated by the compatibility test. \")\nui.print(\"This will be sent along the association request.\")\nui.print(\"They will not be part of the benchmark.\")\ndict_pretty_print(results)\nmsg = \"Please confirm that you would like to associate \"\nmsg += f\"the MLCube '{cube.name}' with the benchmark '{benchmark.name}' [Y/n]\"\napproved = approved or approval_prompt(msg)\nif approved:\nui.print(\"Generating mlcube benchmark association\")\nmetadata = {\"test_result\": results}\ncomms.associate_cube(cube_uid, benchmark_uid, metadata)\nelse:\nui.print(\"MLCube association operation cancelled\")\n
"},{"location":"reference/commands/mlcube/create/","title":"Create","text":""},{"location":"reference/commands/mlcube/create/#commands.mlcube.create.CreateCube","title":"CreateCube","text":"Source code in cli/medperf/commands/mlcube/create.py
class CreateCube:\n@classmethod\ndef run(cls, template_name: str, output_path: str = \".\", config_file: str = None):\n\"\"\"Creates a new MLCube based on one of the provided templates\n        Args:\n            template_name (str): The name of the template to use\n            output_path (str, Optional): The desired path for the MLCube. Defaults to current path.\n            config_file (str, Optional): Path to a JSON configuration file. If not passed, user is prompted.\n        \"\"\"\ntemplate_dirs = config.templates\nif template_name not in template_dirs:\ntemplates = list(template_dirs.keys())\nraise InvalidArgumentError(\nf\"Invalid template name. Available templates: [{' | '.join(templates)}]\"\n)\nno_input = False\nif config_file is not None:\nno_input = True\n# Get package parent path\npath = abspath(Path(__file__).parent.parent.parent)\ntemplate_dir = template_dirs[template_name]\ncookiecutter(\npath,\ndirectory=template_dir,\noutput_dir=output_path,\nconfig_file=config_file,\nno_input=no_input,\n)\n
"},{"location":"reference/commands/mlcube/create/#commands.mlcube.create.CreateCube.run","title":"run(template_name, output_path='.', config_file=None) classmethod","text":"

Creates a new MLCube based on one of the provided templates

Parameters:

Name Type Description Default template_name str

The name of the template to use

required output_path (str, Optional)

The desired path for the MLCube. Defaults to current path.

'.' config_file (str, Optional)

Path to a JSON configuration file. If not passed, user is prompted.

None Source code in cli/medperf/commands/mlcube/create.py
@classmethod\ndef run(cls, template_name: str, output_path: str = \".\", config_file: str = None):\n\"\"\"Creates a new MLCube based on one of the provided templates\n    Args:\n        template_name (str): The name of the template to use\n        output_path (str, Optional): The desired path for the MLCube. Defaults to current path.\n        config_file (str, Optional): Path to a JSON configuration file. If not passed, user is prompted.\n    \"\"\"\ntemplate_dirs = config.templates\nif template_name not in template_dirs:\ntemplates = list(template_dirs.keys())\nraise InvalidArgumentError(\nf\"Invalid template name. Available templates: [{' | '.join(templates)}]\"\n)\nno_input = False\nif config_file is not None:\nno_input = True\n# Get package parent path\npath = abspath(Path(__file__).parent.parent.parent)\ntemplate_dir = template_dirs[template_name]\ncookiecutter(\npath,\ndirectory=template_dir,\noutput_dir=output_path,\nconfig_file=config_file,\nno_input=no_input,\n)\n
"},{"location":"reference/commands/mlcube/mlcube/","title":"Mlcube","text":""},{"location":"reference/commands/mlcube/mlcube/#commands.mlcube.mlcube.associate","title":"associate(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='Benchmark UID'), model_uid=typer.Option(..., '--model_uid', '-m', help='Model UID'), approval=typer.Option(False, '-y', help='Skip approval step'), no_cache=typer.Option(False, '--no-cache', help='Execute the test even if results already exist'))","text":"

Associates an MLCube to a benchmark

Source code in cli/medperf/commands/mlcube/mlcube.py
@app.command(\"associate\")\n@clean_except\ndef associate(\nbenchmark_uid: int = typer.Option(..., \"--benchmark\", \"-b\", help=\"Benchmark UID\"),\nmodel_uid: int = typer.Option(..., \"--model_uid\", \"-m\", help=\"Model UID\"),\napproval: bool = typer.Option(False, \"-y\", help=\"Skip approval step\"),\nno_cache: bool = typer.Option(\nFalse,\n\"--no-cache\",\nhelp=\"Execute the test even if results already exist\",\n),\n):\n\"\"\"Associates an MLCube to a benchmark\"\"\"\nAssociateCube.run(model_uid, benchmark_uid, approved=approval, no_cache=no_cache)\nconfig.ui.print(\"\u2705 Done!\")\n
"},{"location":"reference/commands/mlcube/mlcube/#commands.mlcube.mlcube.create","title":"create(template=typer.Argument(..., help=f'MLCube template name. Available templates: [{' | '.join(config.templates.keys())}]'), output_path=typer.Option('.', '--output', '-o', help='Save the generated MLCube to the specified path'), config_file=typer.Option(None, '--config-file', '-c', help='JSON Configuration file. If not present then user is prompted for configuration'))","text":"

Creates an MLCube based on one of the specified templates

Source code in cli/medperf/commands/mlcube/mlcube.py
@app.command(\"create\")\n@clean_except\ndef create(\ntemplate: str = typer.Argument(\n...,\nhelp=f\"MLCube template name. Available templates: [{' | '.join(config.templates.keys())}]\",\n),\noutput_path: str = typer.Option(\n\".\", \"--output\", \"-o\", help=\"Save the generated MLCube to the specified path\"\n),\nconfig_file: str = typer.Option(\nNone,\n\"--config-file\",\n\"-c\",\nhelp=\"JSON Configuration file. If not present then user is prompted for configuration\",\n),\n):\n\"\"\"Creates an MLCube based on one of the specified templates\"\"\"\nCreateCube.run(template, output_path, config_file)\n
"},{"location":"reference/commands/mlcube/mlcube/#commands.mlcube.mlcube.list","title":"list(local=typer.Option(False, '--local', help='Get local mlcubes'), mine=typer.Option(False, '--mine', help='Get current-user mlcubes'))","text":"

List mlcubes stored locally and remotely from the user

Source code in cli/medperf/commands/mlcube/mlcube.py
@app.command(\"ls\")\n@clean_except\ndef list(\nlocal: bool = typer.Option(False, \"--local\", help=\"Get local mlcubes\"),\nmine: bool = typer.Option(False, \"--mine\", help=\"Get current-user mlcubes\"),\n):\n\"\"\"List mlcubes stored locally and remotely from the user\"\"\"\nEntityList.run(\nCube,\nfields=[\"UID\", \"Name\", \"State\", \"Registered\"],\nlocal_only=local,\nmine_only=mine,\n)\n
"},{"location":"reference/commands/mlcube/mlcube/#commands.mlcube.mlcube.submit","title":"submit(name=typer.Option(..., '--name', '-n', help='Name of the mlcube'), mlcube_file=typer.Option(..., '--mlcube-file', '-m', help='Identifier to download the mlcube file. See the description above'), mlcube_hash=typer.Option('', '--mlcube-hash', help='hash of mlcube file'), parameters_file=typer.Option('', '--parameters-file', '-p', help='Identifier to download the parameters file. See the description above'), parameters_hash=typer.Option('', '--parameters-hash', help='hash of parameters file'), additional_file=typer.Option('', '--additional-file', '-a', help='Identifier to download the additional files tarball. See the description above'), additional_hash=typer.Option('', '--additional-hash', help='hash of additional file'), image_file=typer.Option('', '--image-file', '-i', help='Identifier to download the image file. See the description above'), image_hash=typer.Option('', '--image-hash', help='hash of image file'), operational=typer.Option(False, '--operational', help='Submit the MLCube as OPERATIONAL'))","text":"

Submits a new cube to the platform.

The following assets
  • mlcube_file

  • parameters_file

  • additional_file

  • image_file

are expected to be given in the following format: where source_prefix instructs the client how to download the resource, and resource_identifier is the identifier used to download the asset. The following are supported:

  1. A direct link: \"direct:\"

  2. An asset hosted on the Synapse platform: \"synapse:\"

    If a URL is given without a source prefix, it will be treated as a direct download link.

    Source code in cli/medperf/commands/mlcube/mlcube.py
    @app.command(\"submit\")\n@clean_except\ndef submit(\nname: str = typer.Option(..., \"--name\", \"-n\", help=\"Name of the mlcube\"),\nmlcube_file: str = typer.Option(\n...,\n\"--mlcube-file\",\n\"-m\",\nhelp=\"Identifier to download the mlcube file. See the description above\",\n),\nmlcube_hash: str = typer.Option(\"\", \"--mlcube-hash\", help=\"hash of mlcube file\"),\nparameters_file: str = typer.Option(\n\"\",\n\"--parameters-file\",\n\"-p\",\nhelp=\"Identifier to download the parameters file. See the description above\",\n),\nparameters_hash: str = typer.Option(\n\"\", \"--parameters-hash\", help=\"hash of parameters file\"\n),\nadditional_file: str = typer.Option(\n\"\",\n\"--additional-file\",\n\"-a\",\nhelp=\"Identifier to download the additional files tarball. See the description above\",\n),\nadditional_hash: str = typer.Option(\n\"\", \"--additional-hash\", help=\"hash of additional file\"\n),\nimage_file: str = typer.Option(\n\"\",\n\"--image-file\",\n\"-i\",\nhelp=\"Identifier to download the image file. See the description above\",\n),\nimage_hash: str = typer.Option(\"\", \"--image-hash\", help=\"hash of image file\"),\noperational: bool = typer.Option(\nFalse,\n\"--operational\",\nhelp=\"Submit the MLCube as OPERATIONAL\",\n),\n):\n\"\"\"Submits a new cube to the platform.\\n\n    The following assets:\\n\n        - mlcube_file\\n\n        - parameters_file\\n\n        - additional_file\\n\n        - image_file\\n\n    are expected to be given in the following format: <source_prefix:resource_identifier>\n    where `source_prefix` instructs the client how to download the resource, and `resource_identifier`\n    is the identifier used to download the asset. The following are supported:\\n\n    1. A direct link: \"direct:<URL>\"\\n\n    2. An asset hosted on the Synapse platform: \"synapse:<synapse ID>\"\\n\\n\n    If a URL is given without a source prefix, it will be treated as a direct download link.\n    \"\"\"\nmlcube_info = {\n\"name\": name,\n\"git_mlcube_url\": mlcube_file,\n\"git_mlcube_hash\": mlcube_hash,\n\"git_parameters_url\": parameters_file,\n\"parameters_hash\": parameters_hash,\n\"image_tarball_url\": image_file,\n\"image_tarball_hash\": image_hash,\n\"additional_files_tarball_url\": additional_file,\n\"additional_files_tarball_hash\": additional_hash,\n\"state\": \"OPERATION\" if operational else \"DEVELOPMENT\",\n}\nSubmitCube.run(mlcube_info)\nconfig.ui.print(\"\u2705 Done!\")\n
    "},{"location":"reference/commands/mlcube/mlcube/#commands.mlcube.mlcube.view","title":"view(entity_id=typer.Argument(None, help='MLCube ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local mlcubes if mlcube ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user mlcubes if mlcube ID is not provided'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed'))","text":"

    Displays the information of one or more mlcubes

    Source code in cli/medperf/commands/mlcube/mlcube.py
    @app.command(\"view\")\n@clean_except\ndef view(\nentity_id: Optional[int] = typer.Argument(None, help=\"MLCube ID\"),\nformat: str = typer.Option(\n\"yaml\",\n\"-f\",\n\"--format\",\nhelp=\"Format to display contents. Available formats: [yaml, json]\",\n),\nlocal: bool = typer.Option(\nFalse, \"--local\", help=\"Display local mlcubes if mlcube ID is not provided\"\n),\nmine: bool = typer.Option(\nFalse,\n\"--mine\",\nhelp=\"Display current-user mlcubes if mlcube ID is not provided\",\n),\noutput: str = typer.Option(\nNone,\n\"--output\",\n\"-o\",\nhelp=\"Output file to store contents. If not provided, the output will be displayed\",\n),\n):\n\"\"\"Displays the information of one or more mlcubes\"\"\"\nEntityView.run(entity_id, Cube, format, local, mine, output)\n
    "},{"location":"reference/commands/mlcube/submit/","title":"Submit","text":""},{"location":"reference/commands/mlcube/submit/#commands.mlcube.submit.SubmitCube","title":"SubmitCube","text":"Source code in cli/medperf/commands/mlcube/submit.py
    class SubmitCube:\n@classmethod\ndef run(cls, submit_info: dict):\n\"\"\"Submits a new cube to the medperf platform\n        Args:\n            submit_info (dict): Dictionary containing the cube information.\n        \"\"\"\nui = config.ui\nsubmission = cls(submit_info)\nwith ui.interactive():\nui.text = \"Validating MLCube can be downloaded\"\nsubmission.download()\nui.text = \"Submitting MLCube to MedPerf\"\nupdated_cube_dict = submission.upload()\nsubmission.to_permanent_path(updated_cube_dict)\nsubmission.write(updated_cube_dict)\ndef __init__(self, submit_info: dict):\nself.comms = config.comms\nself.ui = config.ui\nself.cube = Cube(**submit_info)\nconfig.tmp_paths.append(self.cube.path)\ndef download(self):\nself.cube.download_config_files()\nself.cube.download_run_files()\ndef upload(self):\nupdated_body = self.cube.upload()\nreturn updated_body\ndef to_permanent_path(self, cube_dict):\n\"\"\"Renames the temporary cube submission to a permanent one using the uid of\n        the registered cube\n        \"\"\"\nold_cube_loc = self.cube.path\nupdated_cube = Cube(**cube_dict)\nnew_cube_loc = updated_cube.path\nremove_path(new_cube_loc)\nos.rename(old_cube_loc, new_cube_loc)\ndef write(self, updated_cube_dict):\ncube = Cube(**updated_cube_dict)\ncube.write()\n
    "},{"location":"reference/commands/mlcube/submit/#commands.mlcube.submit.SubmitCube.run","title":"run(submit_info) classmethod","text":"

    Submits a new cube to the medperf platform

    Parameters:

    Name Type Description Default submit_info dict

    Dictionary containing the cube information.

    required Source code in cli/medperf/commands/mlcube/submit.py
    @classmethod\ndef run(cls, submit_info: dict):\n\"\"\"Submits a new cube to the medperf platform\n    Args:\n        submit_info (dict): Dictionary containing the cube information.\n    \"\"\"\nui = config.ui\nsubmission = cls(submit_info)\nwith ui.interactive():\nui.text = \"Validating MLCube can be downloaded\"\nsubmission.download()\nui.text = \"Submitting MLCube to MedPerf\"\nupdated_cube_dict = submission.upload()\nsubmission.to_permanent_path(updated_cube_dict)\nsubmission.write(updated_cube_dict)\n
    "},{"location":"reference/commands/mlcube/submit/#commands.mlcube.submit.SubmitCube.to_permanent_path","title":"to_permanent_path(cube_dict)","text":"

    Renames the temporary cube submission to a permanent one using the uid of the registered cube

    Source code in cli/medperf/commands/mlcube/submit.py
    def to_permanent_path(self, cube_dict):\n\"\"\"Renames the temporary cube submission to a permanent one using the uid of\n    the registered cube\n    \"\"\"\nold_cube_loc = self.cube.path\nupdated_cube = Cube(**cube_dict)\nnew_cube_loc = updated_cube.path\nremove_path(new_cube_loc)\nos.rename(old_cube_loc, new_cube_loc)\n
    "},{"location":"reference/commands/result/create/","title":"Create","text":""},{"location":"reference/commands/result/create/#commands.result.create.BenchmarkExecution","title":"BenchmarkExecution","text":"Source code in cli/medperf/commands/result/create.py
    class BenchmarkExecution:\n@classmethod\ndef run(\ncls,\nbenchmark_uid: int,\ndata_uid: int,\nmodels_uids: Optional[List[int]] = None,\nmodels_input_file: Optional[str] = None,\nignore_model_errors=False,\nignore_failed_experiments=False,\nno_cache=False,\nshow_summary=False,\n):\n\"\"\"Benchmark execution flow.\n        Args:\n            benchmark_uid (int): UID of the desired benchmark\n            data_uid (str): Registered Dataset UID\n            models_uids (List|None): list of model UIDs to execute.\n                                    if None, models_input_file will be used\n            models_input_file: filename to read from\n            if models_uids and models_input_file are None, use all benchmark models\n        \"\"\"\nexecution = cls(\nbenchmark_uid,\ndata_uid,\nmodels_uids,\nmodels_input_file,\nignore_model_errors,\nignore_failed_experiments,\n)\nexecution.prepare()\nexecution.validate()\nexecution.prepare_models()\nif not no_cache:\nexecution.load_cached_results()\nwith execution.ui.interactive():\nresults = execution.run_experiments()\nif show_summary:\nexecution.print_summary()\nreturn results\ndef __init__(\nself,\nbenchmark_uid: int,\ndata_uid: int,\nmodels_uids,\nmodels_input_file: str = None,\nignore_model_errors=False,\nignore_failed_experiments=False,\n):\nself.benchmark_uid = benchmark_uid\nself.data_uid = data_uid\nself.models_uids = models_uids\nself.models_input_file = models_input_file\nself.ui = config.ui\nself.evaluator = None\nself.ignore_model_errors = ignore_model_errors\nself.ignore_failed_experiments = ignore_failed_experiments\nself.cached_results = {}\nself.experiments = []\ndef prepare(self):\nself.benchmark = Benchmark.get(self.benchmark_uid)\nself.ui.print(f\"Benchmark Execution: {self.benchmark.name}\")\nself.dataset = Dataset.get(self.data_uid)\nevaluator_uid = self.benchmark.data_evaluator_mlcube\nself.evaluator = self.__get_cube(evaluator_uid, \"Evaluator\")\ndef validate(self):\ndset_prep_cube = self.dataset.data_preparation_mlcube\nbmark_prep_cube = self.benchmark.data_preparation_mlcube\nif self.dataset.id is None:\nmsg = \"The provided dataset is not registered.\"\nraise InvalidArgumentError(msg)\nif self.dataset.state != \"OPERATION\":\nmsg = \"The provided dataset is not operational.\"\nraise InvalidArgumentError(msg)\nif dset_prep_cube != bmark_prep_cube:\nmsg = \"The provided dataset is not compatible with the specified benchmark.\"\nraise InvalidArgumentError(msg)\ndef prepare_models(self):\nif self.models_input_file:\nself.models_uids = self.__get_models_from_file()\nif self.models_uids == [self.benchmark.reference_model_mlcube]:\n# avoid the need of sending a request to the server for\n# finding the benchmark's associated models\nreturn\nbenchmark_models = Benchmark.get_models_uids(self.benchmark_uid)\nbenchmark_models.append(self.benchmark.reference_model_mlcube)\nif self.models_uids is None:\nself.models_uids = benchmark_models\nelse:\nself.__validate_models(benchmark_models)\ndef __get_models_from_file(self):\nif not os.path.exists(self.models_input_file):\nraise InvalidArgumentError(\"The given file does not exist\")\nwith open(self.models_input_file) as f:\ntext = f.read()\nmodels = text.strip().split(\",\")\ntry:\nreturn list(map(int, models))\nexcept ValueError as e:\nmsg = f\"Could not parse the given file: {e}. \"\nmsg += \"The file should contain a list of comma-separated integers\"\nraise InvalidArgumentError(msg)\ndef __validate_models(self, benchmark_models):\nmodels_set = set(self.models_uids)\nbenchmark_models_set = set(benchmark_models)\nnon_assoc_cubes = models_set.difference(benchmark_models_set)\nif non_assoc_cubes:\nif len(non_assoc_cubes) > 1:\nmsg = f\"Model of UID {non_assoc_cubes} is not associated with the specified benchmark.\"\nelse:\nmsg = f\"Models of UIDs {non_assoc_cubes} are not associated with the specified benchmark.\"\nraise InvalidArgumentError(msg)\ndef load_cached_results(self):\nresults = Result.all()\nbenchmark_dset_results = [\nresult\nfor result in results\nif result.benchmark == self.benchmark_uid\nand result.dataset == self.data_uid\n]\nself.cached_results = {\nresult.model: result for result in benchmark_dset_results\n}\ndef __get_cube(self, uid: int, name: str) -> Cube:\nself.ui.text = f\"Retrieving {name} cube\"\ncube = Cube.get(uid)\ncube.download_run_files()\nself.ui.print(f\"> {name} cube download complete\")\nreturn cube\ndef run_experiments(self):\nfor model_uid in self.models_uids:\nif model_uid in self.cached_results:\nself.experiments.append(\n{\n\"model_uid\": model_uid,\n\"result\": self.cached_results[model_uid],\n\"cached\": True,\n\"error\": \"\",\n}\n)\ncontinue\ntry:\nmodel_cube = self.__get_cube(model_uid, \"Model\")\nexecution_summary = Execution.run(\ndataset=self.dataset,\nmodel=model_cube,\nevaluator=self.evaluator,\nignore_model_errors=self.ignore_model_errors,\n)\nexcept MedperfException as e:\nself.__handle_experiment_error(model_uid, e)\nself.experiments.append(\n{\n\"model_uid\": model_uid,\n\"result\": None,\n\"cached\": False,\n\"error\": str(e),\n}\n)\ncontinue\npartial = execution_summary[\"partial\"]\nresults = execution_summary[\"results\"]\nresult = self.__write_result(model_uid, results, partial)\nself.experiments.append(\n{\n\"model_uid\": model_uid,\n\"result\": result,\n\"cached\": False,\n\"error\": \"\",\n}\n)\nreturn [experiment[\"result\"] for experiment in self.experiments]\ndef __handle_experiment_error(self, model_uid, exception):\nif isinstance(exception, InvalidEntityError):\nconfig.ui.print_error(\nf\"There was an error when retrieving the model mlcube {model_uid}: {exception}\"\n)\nelif isinstance(exception, ExecutionError):\nconfig.ui.print_error(\nf\"There was an error when executing the benchmark with the model {model_uid}: {exception}\"\n)\nelse:\nraise exception\nif not self.ignore_failed_experiments:\nraise exception\ndef __result_dict(self, model_uid, results, partial):\nreturn {\n\"name\": f\"b{self.benchmark_uid}m{model_uid}d{self.data_uid}\",\n\"benchmark\": self.benchmark_uid,\n\"model\": model_uid,\n\"dataset\": self.data_uid,\n\"results\": results,\n\"metadata\": {\"partial\": partial},\n}\ndef __write_result(self, model_uid, results, partial):\nresults_info = self.__result_dict(model_uid, results, partial)\nresult = Result(**results_info)\nresult.write()\nreturn result\ndef print_summary(self):\nheaders = [\"model\", \"local result UID\", \"partial result\", \"from cache\", \"error\"]\ndata_lists_for_display = []\nnum_total = len(self.experiments)\nnum_success_run = 0\nnum_failed = 0\nnum_skipped = 0\nnum_partial_skipped = 0\nnum_partial_run = 0\nfor experiment in self.experiments:\n# populate display data\nif experiment[\"result\"]:\ndata_lists_for_display.append(\n[\nexperiment[\"model_uid\"],\nexperiment[\"result\"].generated_uid,\nexperiment[\"result\"].metadata[\"partial\"],\nexperiment[\"cached\"],\nexperiment[\"error\"],\n]\n)\nelse:\ndata_lists_for_display.append(\n[experiment[\"model_uid\"], \"\", \"\", \"\", experiment[\"error\"]]\n)\n# statistics\nif experiment[\"error\"]:\nnum_failed += 1\nelif experiment[\"cached\"]:\nnum_skipped += 1\nif experiment[\"result\"].metadata[\"partial\"]:\nnum_partial_skipped += 1\nelif experiment[\"result\"]:\nnum_success_run += 1\nif experiment[\"result\"].metadata[\"partial\"]:\nnum_partial_run += 1\ntab = tabulate(data_lists_for_display, headers=headers)\nmsg = f\"Total number of models: {num_total}\\n\"\nmsg += f\"\\t{num_skipped} were skipped (already executed), \"\nmsg += f\"of which {num_partial_run} have partial results\\n\"\nmsg += f\"\\t{num_failed} failed\\n\"\nmsg += f\"\\t{num_success_run} ran successfully, \"\nmsg += f\"of which {num_partial_run} have partial results\\n\"\nconfig.ui.print(tab)\nconfig.ui.print(msg)\n
    "},{"location":"reference/commands/result/create/#commands.result.create.BenchmarkExecution.run","title":"run(benchmark_uid, data_uid, models_uids=None, models_input_file=None, ignore_model_errors=False, ignore_failed_experiments=False, no_cache=False, show_summary=False) classmethod","text":"

    Benchmark execution flow.

    Parameters:

    Name Type Description Default benchmark_uid int

    UID of the desired benchmark

    required data_uid str

    Registered Dataset UID

    required models_uids List | None

    list of model UIDs to execute. if None, models_input_file will be used

    None models_input_file Optional[str]

    filename to read from

    None Source code in cli/medperf/commands/result/create.py
    @classmethod\ndef run(\ncls,\nbenchmark_uid: int,\ndata_uid: int,\nmodels_uids: Optional[List[int]] = None,\nmodels_input_file: Optional[str] = None,\nignore_model_errors=False,\nignore_failed_experiments=False,\nno_cache=False,\nshow_summary=False,\n):\n\"\"\"Benchmark execution flow.\n    Args:\n        benchmark_uid (int): UID of the desired benchmark\n        data_uid (str): Registered Dataset UID\n        models_uids (List|None): list of model UIDs to execute.\n                                if None, models_input_file will be used\n        models_input_file: filename to read from\n        if models_uids and models_input_file are None, use all benchmark models\n    \"\"\"\nexecution = cls(\nbenchmark_uid,\ndata_uid,\nmodels_uids,\nmodels_input_file,\nignore_model_errors,\nignore_failed_experiments,\n)\nexecution.prepare()\nexecution.validate()\nexecution.prepare_models()\nif not no_cache:\nexecution.load_cached_results()\nwith execution.ui.interactive():\nresults = execution.run_experiments()\nif show_summary:\nexecution.print_summary()\nreturn results\n
    "},{"location":"reference/commands/result/result/","title":"Result","text":""},{"location":"reference/commands/result/result/#commands.result.result.create","title":"create(benchmark_uid=typer.Option(..., '--benchmark', '-b', help='UID of the desired benchmark'), data_uid=typer.Option(..., '--data_uid', '-d', help='Registered Dataset UID'), model_uid=typer.Option(..., '--model_uid', '-m', help='UID of model to execute'), ignore_model_errors=typer.Option(False, '--ignore-model-errors', help='Ignore failing model cubes, allowing for possibly submitting partial results'), no_cache=typer.Option(False, '--no-cache', help='Execute even if results already exist'))","text":"

    Runs the benchmark execution step for a given benchmark, prepared dataset and model

    Source code in cli/medperf/commands/result/result.py
    @app.command(\"create\")\n@clean_except\ndef create(\nbenchmark_uid: int = typer.Option(\n..., \"--benchmark\", \"-b\", help=\"UID of the desired benchmark\"\n),\ndata_uid: int = typer.Option(\n..., \"--data_uid\", \"-d\", help=\"Registered Dataset UID\"\n),\nmodel_uid: int = typer.Option(\n..., \"--model_uid\", \"-m\", help=\"UID of model to execute\"\n),\nignore_model_errors: bool = typer.Option(\nFalse,\n\"--ignore-model-errors\",\nhelp=\"Ignore failing model cubes, allowing for possibly submitting partial results\",\n),\nno_cache: bool = typer.Option(\nFalse,\n\"--no-cache\",\nhelp=\"Execute even if results already exist\",\n),\n):\n\"\"\"Runs the benchmark execution step for a given benchmark, prepared dataset and model\"\"\"\nBenchmarkExecution.run(\nbenchmark_uid,\ndata_uid,\n[model_uid],\nno_cache=no_cache,\nignore_model_errors=ignore_model_errors,\n)\nconfig.ui.print(\"\u2705 Done!\")\n
    "},{"location":"reference/commands/result/result/#commands.result.result.list","title":"list(local=typer.Option(False, '--local', help='Get local results'), mine=typer.Option(False, '--mine', help='Get current-user results'), benchmark=typer.Option(None, '--benchmark', '-b', help='Get results for a given benchmark'))","text":"

    List results stored locally and remotely from the user

    Source code in cli/medperf/commands/result/result.py
    @app.command(\"ls\")\n@clean_except\ndef list(\nlocal: bool = typer.Option(False, \"--local\", help=\"Get local results\"),\nmine: bool = typer.Option(False, \"--mine\", help=\"Get current-user results\"),\nbenchmark: int = typer.Option(\nNone, \"--benchmark\", \"-b\", help=\"Get results for a given benchmark\"\n),\n):\n\"\"\"List results stored locally and remotely from the user\"\"\"\nEntityList.run(\nResult,\nfields=[\"UID\", \"Benchmark\", \"Model\", \"Dataset\", \"Registered\"],\nlocal_only=local,\nmine_only=mine,\nbenchmark=benchmark,\n)\n
    "},{"location":"reference/commands/result/result/#commands.result.result.submit","title":"submit(result_uid=typer.Option(..., '--result', '-r', help='Unregistered result UID'), approval=typer.Option(False, '-y', help='Skip approval step'))","text":"

    Submits already obtained results to the server

    Source code in cli/medperf/commands/result/result.py
    @app.command(\"submit\")\n@clean_except\ndef submit(\nresult_uid: str = typer.Option(\n..., \"--result\", \"-r\", help=\"Unregistered result UID\"\n),\napproval: bool = typer.Option(False, \"-y\", help=\"Skip approval step\"),\n):\n\"\"\"Submits already obtained results to the server\"\"\"\nResultSubmission.run(result_uid, approved=approval)\nconfig.ui.print(\"\u2705 Done!\")\n
    "},{"location":"reference/commands/result/result/#commands.result.result.view","title":"view(entity_id=typer.Argument(None, help='Result ID'), format=typer.Option('yaml', '-f', '--format', help='Format to display contents. Available formats: [yaml, json]'), local=typer.Option(False, '--local', help='Display local results if result ID is not provided'), mine=typer.Option(False, '--mine', help='Display current-user results if result ID is not provided'), benchmark=typer.Option(None, '--benchmark', '-b', help='Get results for a given benchmark'), output=typer.Option(None, '--output', '-o', help='Output file to store contents. If not provided, the output will be displayed'))","text":"

    Displays the information of one or more results

    Source code in cli/medperf/commands/result/result.py
    @app.command(\"view\")\n@clean_except\ndef view(\nentity_id: Optional[str] = typer.Argument(None, help=\"Result ID\"),\nformat: str = typer.Option(\n\"yaml\",\n\"-f\",\n\"--format\",\nhelp=\"Format to display contents. Available formats: [yaml, json]\",\n),\nlocal: bool = typer.Option(\nFalse, \"--local\", help=\"Display local results if result ID is not provided\"\n),\nmine: bool = typer.Option(\nFalse,\n\"--mine\",\nhelp=\"Display current-user results if result ID is not provided\",\n),\nbenchmark: int = typer.Option(\nNone, \"--benchmark\", \"-b\", help=\"Get results for a given benchmark\"\n),\noutput: str = typer.Option(\nNone,\n\"--output\",\n\"-o\",\nhelp=\"Output file to store contents. If not provided, the output will be displayed\",\n),\n):\n\"\"\"Displays the information of one or more results\"\"\"\nEntityView.run(entity_id, Result, format, local, mine, output, benchmark=benchmark)\n
    "},{"location":"reference/commands/result/submit/","title":"Submit","text":""},{"location":"reference/commands/result/submit/#commands.result.submit.ResultSubmission","title":"ResultSubmission","text":"Source code in cli/medperf/commands/result/submit.py
    class ResultSubmission:\n@classmethod\ndef run(cls, result_uid, approved=False):\nsub = cls(result_uid, approved=approved)\nupdated_result_dict = sub.upload_results()\nsub.to_permanent_path(updated_result_dict)\nsub.write(updated_result_dict)\ndef __init__(self, result_uid, approved=False):\nself.result_uid = result_uid\nself.comms = config.comms\nself.ui = config.ui\nself.approved = approved\ndef request_approval(self, result):\nif result.approval_status == Status.APPROVED:\nreturn True\ndict_pretty_print(result.results)\nself.ui.print(\"Above are the results generated by the model\")\napproved = approval_prompt(\n\"Do you approve uploading the presented results to the MLCommons comms? [Y/n]\"\n)\nreturn approved\ndef upload_results(self):\nresult = Result.get(self.result_uid)\napproved = self.approved or self.request_approval(result)\nif not approved:\nraise CleanExit(\"Results upload operation cancelled\")\nupdated_result_dict = result.upload()\nreturn updated_result_dict\ndef to_permanent_path(self, result_dict: dict):\n\"\"\"Rename the temporary result submission to a permanent one\n        Args:\n            result_dict (dict): updated results dictionary\n        \"\"\"\nresult = Result(**result_dict)\nresult_storage = config.results_folder\nold_res_loc = os.path.join(result_storage, result.generated_uid)\nnew_res_loc = result.path\nremove_path(new_res_loc)\nos.rename(old_res_loc, new_res_loc)\ndef write(self, updated_result_dict):\nresult = Result(**updated_result_dict)\nresult.write()\n
    "},{"location":"reference/commands/result/submit/#commands.result.submit.ResultSubmission.to_permanent_path","title":"to_permanent_path(result_dict)","text":"

    Rename the temporary result submission to a permanent one

    Parameters:

    Name Type Description Default result_dict dict

    updated results dictionary

    required Source code in cli/medperf/commands/result/submit.py
    def to_permanent_path(self, result_dict: dict):\n\"\"\"Rename the temporary result submission to a permanent one\n    Args:\n        result_dict (dict): updated results dictionary\n    \"\"\"\nresult = Result(**result_dict)\nresult_storage = config.results_folder\nold_res_loc = os.path.join(result_storage, result.generated_uid)\nnew_res_loc = result.path\nremove_path(new_res_loc)\nos.rename(old_res_loc, new_res_loc)\n
    "},{"location":"reference/comms/factory/","title":"Factory","text":""},{"location":"reference/comms/interface/","title":"Interface","text":""},{"location":"reference/comms/interface/#comms.interface.Comms","title":"Comms","text":"

    Bases: ABC

    Source code in cli/medperf/comms/interface.py
    class Comms(ABC):\n@abstractmethod\ndef __init__(self, source: str):\n\"\"\"Create an instance of a communication object.\n        Args:\n            source (str): location of the communication source. Where messages are going to be sent.\n            ui (UI): Implementation of the UI interface.\n            token (str, Optional): authentication token to be used throughout communication. Defaults to None.\n        \"\"\"\n@classmethod\n@abstractmethod\ndef parse_url(self, url: str) -> str:\n\"\"\"Parse the source URL so that it can be used by the comms implementation.\n        It should handle protocols and versioning to be able to communicate with the API.\n        Args:\n            url (str): base URL\n        Returns:\n            str: parsed URL with protocol and version\n        \"\"\"\n@abstractmethod\ndef get_current_user(self):\n\"\"\"Retrieve the currently-authenticated user information\"\"\"\n@abstractmethod\ndef get_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks in the platform.\n        Returns:\n            List[dict]: all benchmarks information.\n        \"\"\"\n@abstractmethod\ndef get_benchmark(self, benchmark_uid: int) -> dict:\n\"\"\"Retrieves the benchmark specification file from the server\n        Args:\n            benchmark_uid (int): uid for the desired benchmark\n        Returns:\n            dict: benchmark specification\n        \"\"\"\n@abstractmethod\ndef get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:\n\"\"\"Retrieves all the model associations of a benchmark.\n        Args:\n            benchmark_uid (int): UID of the desired benchmark\n        Returns:\n            list[int]: List of benchmark model associations\n        \"\"\"\n@abstractmethod\ndef get_user_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks created by the user\n        Returns:\n            List[dict]: Benchmarks data\n        \"\"\"\n@abstractmethod\ndef get_cubes(self) -> List[dict]:\n\"\"\"Retrieves all MLCubes in the platform\n        Returns:\n            List[dict]: List containing the data of all MLCubes\n        \"\"\"\n@abstractmethod\ndef get_cube_metadata(self, cube_uid: int) -> dict:\n\"\"\"Retrieves metadata about the specified cube\n        Args:\n            cube_uid (int): UID of the desired cube.\n        Returns:\n            dict: Dictionary containing url and hashes for the cube files\n        \"\"\"\n@abstractmethod\ndef get_user_cubes(self) -> List[dict]:\n\"\"\"Retrieves metadata from all cubes registered by the user\n        Returns:\n            List[dict]: List of dictionaries containing the mlcubes registration information\n        \"\"\"\n@abstractmethod\ndef upload_benchmark(self, benchmark_dict: dict) -> int:\n\"\"\"Uploads a new benchmark to the server.\n        Args:\n            benchmark_dict (dict): benchmark_data to be uploaded\n        Returns:\n            int: UID of newly created benchmark\n        \"\"\"\n@abstractmethod\ndef upload_mlcube(self, mlcube_body: dict) -> int:\n\"\"\"Uploads an MLCube instance to the platform\n        Args:\n            mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes\n        Returns:\n            int: id of the created mlcube instance on the platform\n        \"\"\"\n@abstractmethod\ndef get_datasets(self) -> List[dict]:\n\"\"\"Retrieves all datasets in the platform\n        Returns:\n            List[dict]: List of data from all datasets\n        \"\"\"\n@abstractmethod\ndef get_dataset(self, dset_uid: str) -> dict:\n\"\"\"Retrieves a specific dataset\n        Args:\n            dset_uid (str): Dataset UID\n        Returns:\n            dict: Dataset metadata\n        \"\"\"\n@abstractmethod\ndef get_user_datasets(self) -> dict:\n\"\"\"Retrieves all datasets registered by the user\n        Returns:\n            dict: dictionary with the contents of each dataset registration query\n        \"\"\"\n@abstractmethod\ndef upload_dataset(self, reg_dict: dict) -> int:\n\"\"\"Uploads registration data to the server, under the sha name of the file.\n        Args:\n            reg_dict (dict): Dictionary containing registration information.\n        Returns:\n            int: id of the created dataset registration.\n        \"\"\"\n@abstractmethod\ndef get_results(self) -> List[dict]:\n\"\"\"Retrieves all results\n        Returns:\n            List[dict]: List of results\n        \"\"\"\n@abstractmethod\ndef get_result(self, result_uid: str) -> dict:\n\"\"\"Retrieves a specific result data\n        Args:\n            result_uid (str): Result UID\n        Returns:\n            dict: Result metadata\n        \"\"\"\n@abstractmethod\ndef get_user_results(self) -> dict:\n\"\"\"Retrieves all results registered by the user\n        Returns:\n            dict: dictionary with the contents of each dataset registration query\n        \"\"\"\n@abstractmethod\ndef get_benchmark_results(self, benchmark_id: int) -> dict:\n\"\"\"Retrieves all results for a given benchmark\n        Args:\n            benchmark_id (int): benchmark ID to retrieve results from\n        Returns:\n            dict: dictionary with the contents of each result in the specified benchmark\n        \"\"\"\n@abstractmethod\ndef upload_result(self, results_dict: dict) -> int:\n\"\"\"Uploads result to the server.\n        Args:\n            results_dict (dict): Dictionary containing results information.\n        Returns:\n            int: id of the generated results entry\n        \"\"\"\n@abstractmethod\ndef associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create a Dataset Benchmark association\n        Args:\n            data_uid (int): Registered dataset UID\n            benchmark_uid (int): Benchmark UID\n            metadata (dict, optional): Additional metadata. Defaults to {}.\n        \"\"\"\n@abstractmethod\ndef associate_cube(self, cube_uid: str, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create an MLCube-Benchmark association\n        Args:\n            cube_uid (str): MLCube UID\n            benchmark_uid (int): Benchmark UID\n            metadata (dict, optional): Additional metadata. Defaults to {}.\n        \"\"\"\n@abstractmethod\ndef set_dataset_association_approval(\nself, dataset_uid: str, benchmark_uid: str, status: str\n):\n\"\"\"Approves a dataset association\n        Args:\n            dataset_uid (str): Dataset UID\n            benchmark_uid (str): Benchmark UID\n            status (str): Approval status to set for the association\n        \"\"\"\n@abstractmethod\ndef set_mlcube_association_approval(\nself, mlcube_uid: str, benchmark_uid: str, status: str\n):\n\"\"\"Approves an mlcube association\n        Args:\n            mlcube_uid (str): Dataset UID\n            benchmark_uid (str): Benchmark UID\n            status (str): Approval status to set for the association\n        \"\"\"\n@abstractmethod\ndef get_datasets_associations(self) -> List[dict]:\n\"\"\"Get all dataset associations related to the current user\n        Returns:\n            List[dict]: List containing all associations information\n        \"\"\"\n@abstractmethod\ndef get_cubes_associations(self) -> List[dict]:\n\"\"\"Get all cube associations related to the current user\n        Returns:\n            List[dict]: List containing all associations information\n        \"\"\"\n@abstractmethod\ndef set_mlcube_association_priority(\nself, benchmark_uid: str, mlcube_uid: str, priority: int\n):\n\"\"\"Sets the priority of an mlcube-benchmark association\n        Args:\n            mlcube_uid (str): MLCube UID\n            benchmark_uid (str): Benchmark UID\n            priority (int): priority value to set for the association\n        \"\"\"\n@abstractmethod\ndef update_dataset(self, dataset_id: int, data: dict):\n\"\"\"Updates the contents of a datasets identified by dataset_id to the new data dictionary.\n        Updates may be partial.\n        Args:\n            dataset_id (int): ID of the dataset to update\n            data (dict): Updated information of the dataset.\n        \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.__init__","title":"__init__(source) abstractmethod","text":"

    Create an instance of a communication object.

    Parameters:

    Name Type Description Default source str

    location of the communication source. Where messages are going to be sent.

    required ui UI

    Implementation of the UI interface.

    required token (str, Optional)

    authentication token to be used throughout communication. Defaults to None.

    required Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef __init__(self, source: str):\n\"\"\"Create an instance of a communication object.\n    Args:\n        source (str): location of the communication source. Where messages are going to be sent.\n        ui (UI): Implementation of the UI interface.\n        token (str, Optional): authentication token to be used throughout communication. Defaults to None.\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.associate_cube","title":"associate_cube(cube_uid, benchmark_uid, metadata={}) abstractmethod","text":"

    Create an MLCube-Benchmark association

    Parameters:

    Name Type Description Default cube_uid str

    MLCube UID

    required benchmark_uid int

    Benchmark UID

    required metadata dict

    Additional metadata. Defaults to {}.

    {} Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef associate_cube(self, cube_uid: str, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create an MLCube-Benchmark association\n    Args:\n        cube_uid (str): MLCube UID\n        benchmark_uid (int): Benchmark UID\n        metadata (dict, optional): Additional metadata. Defaults to {}.\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.associate_dset","title":"associate_dset(data_uid, benchmark_uid, metadata={}) abstractmethod","text":"

    Create a Dataset Benchmark association

    Parameters:

    Name Type Description Default data_uid int

    Registered dataset UID

    required benchmark_uid int

    Benchmark UID

    required metadata dict

    Additional metadata. Defaults to {}.

    {} Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create a Dataset Benchmark association\n    Args:\n        data_uid (int): Registered dataset UID\n        benchmark_uid (int): Benchmark UID\n        metadata (dict, optional): Additional metadata. Defaults to {}.\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_benchmark","title":"get_benchmark(benchmark_uid) abstractmethod","text":"

    Retrieves the benchmark specification file from the server

    Parameters:

    Name Type Description Default benchmark_uid int

    uid for the desired benchmark

    required

    Returns:

    Name Type Description dict dict

    benchmark specification

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_benchmark(self, benchmark_uid: int) -> dict:\n\"\"\"Retrieves the benchmark specification file from the server\n    Args:\n        benchmark_uid (int): uid for the desired benchmark\n    Returns:\n        dict: benchmark specification\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_benchmark_model_associations","title":"get_benchmark_model_associations(benchmark_uid) abstractmethod","text":"

    Retrieves all the model associations of a benchmark.

    Parameters:

    Name Type Description Default benchmark_uid int

    UID of the desired benchmark

    required

    Returns:

    Type Description List[int]

    list[int]: List of benchmark model associations

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:\n\"\"\"Retrieves all the model associations of a benchmark.\n    Args:\n        benchmark_uid (int): UID of the desired benchmark\n    Returns:\n        list[int]: List of benchmark model associations\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_benchmark_results","title":"get_benchmark_results(benchmark_id) abstractmethod","text":"

    Retrieves all results for a given benchmark

    Parameters:

    Name Type Description Default benchmark_id int

    benchmark ID to retrieve results from

    required

    Returns:

    Name Type Description dict dict

    dictionary with the contents of each result in the specified benchmark

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_benchmark_results(self, benchmark_id: int) -> dict:\n\"\"\"Retrieves all results for a given benchmark\n    Args:\n        benchmark_id (int): benchmark ID to retrieve results from\n    Returns:\n        dict: dictionary with the contents of each result in the specified benchmark\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_benchmarks","title":"get_benchmarks() abstractmethod","text":"

    Retrieves all benchmarks in the platform.

    Returns:

    Type Description List[dict]

    List[dict]: all benchmarks information.

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks in the platform.\n    Returns:\n        List[dict]: all benchmarks information.\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_cube_metadata","title":"get_cube_metadata(cube_uid) abstractmethod","text":"

    Retrieves metadata about the specified cube

    Parameters:

    Name Type Description Default cube_uid int

    UID of the desired cube.

    required

    Returns:

    Name Type Description dict dict

    Dictionary containing url and hashes for the cube files

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_cube_metadata(self, cube_uid: int) -> dict:\n\"\"\"Retrieves metadata about the specified cube\n    Args:\n        cube_uid (int): UID of the desired cube.\n    Returns:\n        dict: Dictionary containing url and hashes for the cube files\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_cubes","title":"get_cubes() abstractmethod","text":"

    Retrieves all MLCubes in the platform

    Returns:

    Type Description List[dict]

    List[dict]: List containing the data of all MLCubes

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_cubes(self) -> List[dict]:\n\"\"\"Retrieves all MLCubes in the platform\n    Returns:\n        List[dict]: List containing the data of all MLCubes\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_cubes_associations","title":"get_cubes_associations() abstractmethod","text":"

    Get all cube associations related to the current user

    Returns:

    Type Description List[dict]

    List[dict]: List containing all associations information

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_cubes_associations(self) -> List[dict]:\n\"\"\"Get all cube associations related to the current user\n    Returns:\n        List[dict]: List containing all associations information\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_current_user","title":"get_current_user() abstractmethod","text":"

    Retrieve the currently-authenticated user information

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_current_user(self):\n\"\"\"Retrieve the currently-authenticated user information\"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_dataset","title":"get_dataset(dset_uid) abstractmethod","text":"

    Retrieves a specific dataset

    Parameters:

    Name Type Description Default dset_uid str

    Dataset UID

    required

    Returns:

    Name Type Description dict dict

    Dataset metadata

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_dataset(self, dset_uid: str) -> dict:\n\"\"\"Retrieves a specific dataset\n    Args:\n        dset_uid (str): Dataset UID\n    Returns:\n        dict: Dataset metadata\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_datasets","title":"get_datasets() abstractmethod","text":"

    Retrieves all datasets in the platform

    Returns:

    Type Description List[dict]

    List[dict]: List of data from all datasets

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_datasets(self) -> List[dict]:\n\"\"\"Retrieves all datasets in the platform\n    Returns:\n        List[dict]: List of data from all datasets\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_datasets_associations","title":"get_datasets_associations() abstractmethod","text":"

    Get all dataset associations related to the current user

    Returns:

    Type Description List[dict]

    List[dict]: List containing all associations information

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_datasets_associations(self) -> List[dict]:\n\"\"\"Get all dataset associations related to the current user\n    Returns:\n        List[dict]: List containing all associations information\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_result","title":"get_result(result_uid) abstractmethod","text":"

    Retrieves a specific result data

    Parameters:

    Name Type Description Default result_uid str

    Result UID

    required

    Returns:

    Name Type Description dict dict

    Result metadata

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_result(self, result_uid: str) -> dict:\n\"\"\"Retrieves a specific result data\n    Args:\n        result_uid (str): Result UID\n    Returns:\n        dict: Result metadata\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_results","title":"get_results() abstractmethod","text":"

    Retrieves all results

    Returns:

    Type Description List[dict]

    List[dict]: List of results

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_results(self) -> List[dict]:\n\"\"\"Retrieves all results\n    Returns:\n        List[dict]: List of results\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_user_benchmarks","title":"get_user_benchmarks() abstractmethod","text":"

    Retrieves all benchmarks created by the user

    Returns:

    Type Description List[dict]

    List[dict]: Benchmarks data

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_user_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks created by the user\n    Returns:\n        List[dict]: Benchmarks data\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_user_cubes","title":"get_user_cubes() abstractmethod","text":"

    Retrieves metadata from all cubes registered by the user

    Returns:

    Type Description List[dict]

    List[dict]: List of dictionaries containing the mlcubes registration information

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_user_cubes(self) -> List[dict]:\n\"\"\"Retrieves metadata from all cubes registered by the user\n    Returns:\n        List[dict]: List of dictionaries containing the mlcubes registration information\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_user_datasets","title":"get_user_datasets() abstractmethod","text":"

    Retrieves all datasets registered by the user

    Returns:

    Name Type Description dict dict

    dictionary with the contents of each dataset registration query

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_user_datasets(self) -> dict:\n\"\"\"Retrieves all datasets registered by the user\n    Returns:\n        dict: dictionary with the contents of each dataset registration query\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.get_user_results","title":"get_user_results() abstractmethod","text":"

    Retrieves all results registered by the user

    Returns:

    Name Type Description dict dict

    dictionary with the contents of each dataset registration query

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef get_user_results(self) -> dict:\n\"\"\"Retrieves all results registered by the user\n    Returns:\n        dict: dictionary with the contents of each dataset registration query\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.parse_url","title":"parse_url(url) abstractmethod classmethod","text":"

    Parse the source URL so that it can be used by the comms implementation. It should handle protocols and versioning to be able to communicate with the API.

    Parameters:

    Name Type Description Default url str

    base URL

    required

    Returns:

    Name Type Description str str

    parsed URL with protocol and version

    Source code in cli/medperf/comms/interface.py
    @classmethod\n@abstractmethod\ndef parse_url(self, url: str) -> str:\n\"\"\"Parse the source URL so that it can be used by the comms implementation.\n    It should handle protocols and versioning to be able to communicate with the API.\n    Args:\n        url (str): base URL\n    Returns:\n        str: parsed URL with protocol and version\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.set_dataset_association_approval","title":"set_dataset_association_approval(dataset_uid, benchmark_uid, status) abstractmethod","text":"

    Approves a dataset association

    Parameters:

    Name Type Description Default dataset_uid str

    Dataset UID

    required benchmark_uid str

    Benchmark UID

    required status str

    Approval status to set for the association

    required Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef set_dataset_association_approval(\nself, dataset_uid: str, benchmark_uid: str, status: str\n):\n\"\"\"Approves a dataset association\n    Args:\n        dataset_uid (str): Dataset UID\n        benchmark_uid (str): Benchmark UID\n        status (str): Approval status to set for the association\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.set_mlcube_association_approval","title":"set_mlcube_association_approval(mlcube_uid, benchmark_uid, status) abstractmethod","text":"

    Approves an mlcube association

    Parameters:

    Name Type Description Default mlcube_uid str

    Dataset UID

    required benchmark_uid str

    Benchmark UID

    required status str

    Approval status to set for the association

    required Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef set_mlcube_association_approval(\nself, mlcube_uid: str, benchmark_uid: str, status: str\n):\n\"\"\"Approves an mlcube association\n    Args:\n        mlcube_uid (str): Dataset UID\n        benchmark_uid (str): Benchmark UID\n        status (str): Approval status to set for the association\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.set_mlcube_association_priority","title":"set_mlcube_association_priority(benchmark_uid, mlcube_uid, priority) abstractmethod","text":"

    Sets the priority of an mlcube-benchmark association

    Parameters:

    Name Type Description Default mlcube_uid str

    MLCube UID

    required benchmark_uid str

    Benchmark UID

    required priority int

    priority value to set for the association

    required Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef set_mlcube_association_priority(\nself, benchmark_uid: str, mlcube_uid: str, priority: int\n):\n\"\"\"Sets the priority of an mlcube-benchmark association\n    Args:\n        mlcube_uid (str): MLCube UID\n        benchmark_uid (str): Benchmark UID\n        priority (int): priority value to set for the association\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.update_dataset","title":"update_dataset(dataset_id, data) abstractmethod","text":"

    Updates the contents of a datasets identified by dataset_id to the new data dictionary. Updates may be partial.

    Parameters:

    Name Type Description Default dataset_id int

    ID of the dataset to update

    required data dict

    Updated information of the dataset.

    required Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef update_dataset(self, dataset_id: int, data: dict):\n\"\"\"Updates the contents of a datasets identified by dataset_id to the new data dictionary.\n    Updates may be partial.\n    Args:\n        dataset_id (int): ID of the dataset to update\n        data (dict): Updated information of the dataset.\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.upload_benchmark","title":"upload_benchmark(benchmark_dict) abstractmethod","text":"

    Uploads a new benchmark to the server.

    Parameters:

    Name Type Description Default benchmark_dict dict

    benchmark_data to be uploaded

    required

    Returns:

    Name Type Description int int

    UID of newly created benchmark

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef upload_benchmark(self, benchmark_dict: dict) -> int:\n\"\"\"Uploads a new benchmark to the server.\n    Args:\n        benchmark_dict (dict): benchmark_data to be uploaded\n    Returns:\n        int: UID of newly created benchmark\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.upload_dataset","title":"upload_dataset(reg_dict) abstractmethod","text":"

    Uploads registration data to the server, under the sha name of the file.

    Parameters:

    Name Type Description Default reg_dict dict

    Dictionary containing registration information.

    required

    Returns:

    Name Type Description int int

    id of the created dataset registration.

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef upload_dataset(self, reg_dict: dict) -> int:\n\"\"\"Uploads registration data to the server, under the sha name of the file.\n    Args:\n        reg_dict (dict): Dictionary containing registration information.\n    Returns:\n        int: id of the created dataset registration.\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.upload_mlcube","title":"upload_mlcube(mlcube_body) abstractmethod","text":"

    Uploads an MLCube instance to the platform

    Parameters:

    Name Type Description Default mlcube_body dict

    Dictionary containing all the relevant data for creating mlcubes

    required

    Returns:

    Name Type Description int int

    id of the created mlcube instance on the platform

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef upload_mlcube(self, mlcube_body: dict) -> int:\n\"\"\"Uploads an MLCube instance to the platform\n    Args:\n        mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes\n    Returns:\n        int: id of the created mlcube instance on the platform\n    \"\"\"\n
    "},{"location":"reference/comms/interface/#comms.interface.Comms.upload_result","title":"upload_result(results_dict) abstractmethod","text":"

    Uploads result to the server.

    Parameters:

    Name Type Description Default results_dict dict

    Dictionary containing results information.

    required

    Returns:

    Name Type Description int int

    id of the generated results entry

    Source code in cli/medperf/comms/interface.py
    @abstractmethod\ndef upload_result(self, results_dict: dict) -> int:\n\"\"\"Uploads result to the server.\n    Args:\n        results_dict (dict): Dictionary containing results information.\n    Returns:\n        int: id of the generated results entry\n    \"\"\"\n
    "},{"location":"reference/comms/rest/","title":"Rest","text":""},{"location":"reference/comms/rest/#comms.rest.REST","title":"REST","text":"

    Bases: Comms

    Source code in cli/medperf/comms/rest.py
    class REST(Comms):\ndef __init__(self, source: str):\nself.server_url = self.parse_url(source)\nself.cert = config.certificate\nif self.cert is None:\n# No certificate provided, default to normal verification\nself.cert = True\n@classmethod\ndef parse_url(cls, url: str) -> str:\n\"\"\"Parse the source URL so that it can be used by the comms implementation.\n        It should handle protocols and versioning to be able to communicate with the API.\n        Args:\n            url (str): base URL\n        Returns:\n            str: parsed URL with protocol and version\n        \"\"\"\nurl_sections = url.split(\"://\")\napi_path = f\"/api/v{config.major_version}\"\n# Remove protocol if passed\nif len(url_sections) > 1:\nurl = \"\".join(url_sections[1:])\nreturn f\"https://{url}{api_path}\"\ndef __auth_get(self, url, **kwargs):\nreturn self.__auth_req(url, requests.get, **kwargs)\ndef __auth_post(self, url, **kwargs):\nreturn self.__auth_req(url, requests.post, **kwargs)\ndef __auth_put(self, url, **kwargs):\nreturn self.__auth_req(url, requests.put, **kwargs)\ndef __auth_req(self, url, req_func, **kwargs):\ntoken = config.auth.access_token\nreturn self.__req(\nurl, req_func, headers={\"Authorization\": f\"Bearer {token}\"}, **kwargs\n)\ndef __req(self, url, req_func, **kwargs):\nlogging.debug(f\"Calling {req_func}: {url}\")\nif \"json\" in kwargs:\nlogging.debug(f\"Passing JSON contents: {kwargs['json']}\")\nkwargs[\"json\"] = sanitize_json(kwargs[\"json\"])\ntry:\nreturn req_func(url, verify=self.cert, **kwargs)\nexcept requests.exceptions.SSLError as e:\nlogging.error(f\"Couldn't connect to {self.server_url}: {e}\")\nraise CommunicationError(\n\"Couldn't connect to server through HTTPS. If running locally, \"\n\"remember to provide the server certificate through --certificate\"\n)\ndef __get_list(\nself,\nurl,\nnum_elements=None,\npage_size=config.default_page_size,\noffset=0,\nbinary_reduction=False,\n):\n\"\"\"Retrieves a list of elements from a URL by iterating over pages until num_elements is obtained.\n        If num_elements is None, then iterates until all elements have been retrieved.\n        If binary_reduction is enabled, errors are assumed to be related to response size. In that case,\n        the page_size is reduced by half until a successful response is obtained or until page_size can't be\n        reduced anymore.\n        Args:\n            url (str): The url to retrieve elements from\n            num_elements (int, optional): The desired number of elements to be retrieved. Defaults to None.\n            page_size (int, optional): Starting page size. Defaults to config.default_page_size.\n            start_limit (int, optional): The starting position for element retrieval. Defaults to 0.\n            binary_reduction (bool, optional): Wether to handle errors by halfing the page size. Defaults to False.\n        Returns:\n            List[dict]: A list of dictionaries representing the retrieved elements.\n        \"\"\"\nel_list = []\nif num_elements is None:\nnum_elements = float(\"inf\")\nwhile len(el_list) < num_elements:\npaginated_url = f\"{url}?limit={page_size}&offset={offset}\"\nres = self.__auth_get(paginated_url)\nif res.status_code != 200:\nif not binary_reduction:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"there was an error retrieving the current list: {details}\"\n)\nlog_response_error(res, warn=True)\ndetails = format_errors_dict(res.json())\nif page_size <= 1:\nraise CommunicationRetrievalError(\nf\"Could not retrieve list. Minimum page size achieved without success: {details}\"\n)\npage_size = page_size // 2\ncontinue\nelse:\ndata = res.json()\nel_list += data[\"results\"]\noffset += len(data[\"results\"])\nif data[\"next\"] is None:\nbreak\nif isinstance(num_elements, int):\nreturn el_list[:num_elements]\nreturn el_list\ndef __set_approval_status(self, url: str, status: str) -> requests.Response:\n\"\"\"Sets the approval status of a resource\n        Args:\n            url (str): URL to the resource to update\n            status (str): approval status to set\n        Returns:\n            requests.Response: Response object returned by the update\n        \"\"\"\ndata = {\"approval_status\": status}\nres = self.__auth_put(url, json=data)\nreturn res\ndef get_current_user(self):\n\"\"\"Retrieve the currently-authenticated user information\"\"\"\nres = self.__auth_get(f\"{self.server_url}/me/\")\nreturn res.json()\ndef get_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks in the platform.\n        Returns:\n            List[dict]: all benchmarks information.\n        \"\"\"\nbmks = self.__get_list(f\"{self.server_url}/benchmarks/\")\nreturn bmks\ndef get_benchmark(self, benchmark_uid: int) -> dict:\n\"\"\"Retrieves the benchmark specification file from the server\n        Args:\n            benchmark_uid (int): uid for the desired benchmark\n        Returns:\n            dict: benchmark specification\n        \"\"\"\nres = self.__auth_get(f\"{self.server_url}/benchmarks/{benchmark_uid}\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"the specified benchmark doesn't exist: {details}\"\n)\nreturn res.json()\ndef get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:\n\"\"\"Retrieves all the model associations of a benchmark.\n        Args:\n            benchmark_uid (int): UID of the desired benchmark\n        Returns:\n            list[int]: List of benchmark model associations\n        \"\"\"\nassocs = self.__get_list(f\"{self.server_url}/benchmarks/{benchmark_uid}/models\")\nreturn filter_latest_associations(assocs, \"model_mlcube\")\ndef get_user_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks created by the user\n        Returns:\n            List[dict]: Benchmarks data\n        \"\"\"\nbmks = self.__get_list(f\"{self.server_url}/me/benchmarks/\")\nreturn bmks\ndef get_cubes(self) -> List[dict]:\n\"\"\"Retrieves all MLCubes in the platform\n        Returns:\n            List[dict]: List containing the data of all MLCubes\n        \"\"\"\ncubes = self.__get_list(f\"{self.server_url}/mlcubes/\")\nreturn cubes\ndef get_cube_metadata(self, cube_uid: int) -> dict:\n\"\"\"Retrieves metadata about the specified cube\n        Args:\n            cube_uid (int): UID of the desired cube.\n        Returns:\n            dict: Dictionary containing url and hashes for the cube files\n        \"\"\"\nres = self.__auth_get(f\"{self.server_url}/mlcubes/{cube_uid}/\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"the specified cube doesn't exist {details}\"\n)\nreturn res.json()\ndef get_user_cubes(self) -> List[dict]:\n\"\"\"Retrieves metadata from all cubes registered by the user\n        Returns:\n            List[dict]: List of dictionaries containing the mlcubes registration information\n        \"\"\"\ncubes = self.__get_list(f\"{self.server_url}/me/mlcubes/\")\nreturn cubes\ndef upload_benchmark(self, benchmark_dict: dict) -> int:\n\"\"\"Uploads a new benchmark to the server.\n        Args:\n            benchmark_dict (dict): benchmark_data to be uploaded\n        Returns:\n            int: UID of newly created benchmark\n        \"\"\"\nres = self.__auth_post(f\"{self.server_url}/benchmarks/\", json=benchmark_dict)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(f\"Could not upload benchmark: {details}\")\nreturn res.json()\ndef upload_mlcube(self, mlcube_body: dict) -> int:\n\"\"\"Uploads an MLCube instance to the platform\n        Args:\n            mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes\n        Returns:\n            int: id of the created mlcube instance on the platform\n        \"\"\"\nres = self.__auth_post(f\"{self.server_url}/mlcubes/\", json=mlcube_body)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(f\"Could not upload the mlcube: {details}\")\nreturn res.json()\ndef get_datasets(self) -> List[dict]:\n\"\"\"Retrieves all datasets in the platform\n        Returns:\n            List[dict]: List of data from all datasets\n        \"\"\"\ndsets = self.__get_list(f\"{self.server_url}/datasets/\")\nreturn dsets\ndef get_dataset(self, dset_uid: int) -> dict:\n\"\"\"Retrieves a specific dataset\n        Args:\n            dset_uid (int): Dataset UID\n        Returns:\n            dict: Dataset metadata\n        \"\"\"\nres = self.__auth_get(f\"{self.server_url}/datasets/{dset_uid}/\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"Could not retrieve the specified dataset from server: {details}\"\n)\nreturn res.json()\ndef get_user_datasets(self) -> dict:\n\"\"\"Retrieves all datasets registered by the user\n        Returns:\n            dict: dictionary with the contents of each dataset registration query\n        \"\"\"\ndsets = self.__get_list(f\"{self.server_url}/me/datasets/\")\nreturn dsets\ndef upload_dataset(self, reg_dict: dict) -> int:\n\"\"\"Uploads registration data to the server, under the sha name of the file.\n        Args:\n            reg_dict (dict): Dictionary containing registration information.\n        Returns:\n            int: id of the created dataset registration.\n        \"\"\"\nres = self.__auth_post(f\"{self.server_url}/datasets/\", json=reg_dict)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(f\"Could not upload the dataset: {details}\")\nreturn res.json()\ndef get_results(self) -> List[dict]:\n\"\"\"Retrieves all results\n        Returns:\n            List[dict]: List of results\n        \"\"\"\nres = self.__get_list(f\"{self.server_url}/results\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(f\"Could not retrieve results: {details}\")\nreturn res.json()\ndef get_result(self, result_uid: int) -> dict:\n\"\"\"Retrieves a specific result data\n        Args:\n            result_uid (int): Result UID\n        Returns:\n            dict: Result metadata\n        \"\"\"\nres = self.__auth_get(f\"{self.server_url}/results/{result_uid}/\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"Could not retrieve the specified result: {details}\"\n)\nreturn res.json()\ndef get_user_results(self) -> dict:\n\"\"\"Retrieves all results registered by the user\n        Returns:\n            dict: dictionary with the contents of each result registration query\n        \"\"\"\nresults = self.__get_list(f\"{self.server_url}/me/results/\")\nreturn results\ndef get_benchmark_results(self, benchmark_id: int) -> dict:\n\"\"\"Retrieves all results for a given benchmark\n        Args:\n            benchmark_id (int): benchmark ID to retrieve results from\n        Returns:\n            dict: dictionary with the contents of each result in the specified benchmark\n        \"\"\"\nresults = self.__get_list(\nf\"{self.server_url}/benchmarks/{benchmark_id}/results\"\n)\nreturn results\ndef upload_result(self, results_dict: dict) -> int:\n\"\"\"Uploads result to the server.\n        Args:\n            results_dict (dict): Dictionary containing results information.\n        Returns:\n            int: id of the generated results entry\n        \"\"\"\nres = self.__auth_post(f\"{self.server_url}/results/\", json=results_dict)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(f\"Could not upload the results: {details}\")\nreturn res.json()\ndef associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create a Dataset Benchmark association\n        Args:\n            data_uid (int): Registered dataset UID\n            benchmark_uid (int): Benchmark UID\n            metadata (dict, optional): Additional metadata. Defaults to {}.\n        \"\"\"\ndata = {\n\"dataset\": data_uid,\n\"benchmark\": benchmark_uid,\n\"approval_status\": Status.PENDING.value,\n\"metadata\": metadata,\n}\nres = self.__auth_post(f\"{self.server_url}/datasets/benchmarks/\", json=data)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not associate dataset to benchmark: {details}\"\n)\ndef associate_cube(self, cube_uid: int, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create an MLCube-Benchmark association\n        Args:\n            cube_uid (int): MLCube UID\n            benchmark_uid (int): Benchmark UID\n            metadata (dict, optional): Additional metadata. Defaults to {}.\n        \"\"\"\ndata = {\n\"approval_status\": Status.PENDING.value,\n\"model_mlcube\": cube_uid,\n\"benchmark\": benchmark_uid,\n\"metadata\": metadata,\n}\nres = self.__auth_post(f\"{self.server_url}/mlcubes/benchmarks/\", json=data)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not associate mlcube to benchmark: {details}\"\n)\ndef set_dataset_association_approval(\nself, benchmark_uid: int, dataset_uid: int, status: str\n):\n\"\"\"Approves a dataset association\n        Args:\n            dataset_uid (int): Dataset UID\n            benchmark_uid (int): Benchmark UID\n            status (str): Approval status to set for the association\n        \"\"\"\nurl = f\"{self.server_url}/datasets/{dataset_uid}/benchmarks/{benchmark_uid}/\"\nres = self.__set_approval_status(url, status)\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not approve association between dataset {dataset_uid} and benchmark {benchmark_uid}: {details}\"\n)\ndef set_mlcube_association_approval(\nself, benchmark_uid: int, mlcube_uid: int, status: str\n):\n\"\"\"Approves an mlcube association\n        Args:\n            mlcube_uid (int): Dataset UID\n            benchmark_uid (int): Benchmark UID\n            status (str): Approval status to set for the association\n        \"\"\"\nurl = f\"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/\"\nres = self.__set_approval_status(url, status)\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not approve association between mlcube {mlcube_uid} and benchmark {benchmark_uid}: {details}\"\n)\ndef get_datasets_associations(self) -> List[dict]:\n\"\"\"Get all dataset associations related to the current user\n        Returns:\n            List[dict]: List containing all associations information\n        \"\"\"\nassocs = self.__get_list(f\"{self.server_url}/me/datasets/associations/\")\nreturn filter_latest_associations(assocs, \"dataset\")\ndef get_cubes_associations(self) -> List[dict]:\n\"\"\"Get all cube associations related to the current user\n        Returns:\n            List[dict]: List containing all associations information\n        \"\"\"\nassocs = self.__get_list(f\"{self.server_url}/me/mlcubes/associations/\")\nreturn filter_latest_associations(assocs, \"model_mlcube\")\ndef set_mlcube_association_priority(\nself, benchmark_uid: int, mlcube_uid: int, priority: int\n):\n\"\"\"Sets the priority of an mlcube-benchmark association\n        Args:\n            mlcube_uid (int): MLCube UID\n            benchmark_uid (int): Benchmark UID\n            priority (int): priority value to set for the association\n        \"\"\"\nurl = f\"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/\"\ndata = {\"priority\": priority}\nres = self.__auth_put(url, json=data)\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not set the priority of mlcube {mlcube_uid} within the benchmark {benchmark_uid}: {details}\"\n)\ndef update_dataset(self, dataset_id: int, data: dict):\nurl = f\"{self.server_url}/datasets/{dataset_id}/\"\nres = self.__auth_put(url, json=data)\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(f\"Could not update dataset: {details}\")\nreturn res.json()\ndef get_mlcube_datasets(self, mlcube_id: int) -> dict:\n\"\"\"Retrieves all datasets that have the specified mlcube as the prep mlcube\n        Args:\n            mlcube_id (int): mlcube ID to retrieve datasets from\n        Returns:\n            dict: dictionary with the contents of each dataset\n        \"\"\"\ndatasets = self.__get_list(f\"{self.server_url}/mlcubes/{mlcube_id}/datasets/\")\nreturn datasets\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.__get_list","title":"__get_list(url, num_elements=None, page_size=config.default_page_size, offset=0, binary_reduction=False)","text":"

    Retrieves a list of elements from a URL by iterating over pages until num_elements is obtained. If num_elements is None, then iterates until all elements have been retrieved. If binary_reduction is enabled, errors are assumed to be related to response size. In that case, the page_size is reduced by half until a successful response is obtained or until page_size can't be reduced anymore.

    Parameters:

    Name Type Description Default url str

    The url to retrieve elements from

    required num_elements int

    The desired number of elements to be retrieved. Defaults to None.

    None page_size int

    Starting page size. Defaults to config.default_page_size.

    config.default_page_size start_limit int

    The starting position for element retrieval. Defaults to 0.

    required binary_reduction bool

    Wether to handle errors by halfing the page size. Defaults to False.

    False

    Returns:

    Type Description

    List[dict]: A list of dictionaries representing the retrieved elements.

    Source code in cli/medperf/comms/rest.py
    def __get_list(\nself,\nurl,\nnum_elements=None,\npage_size=config.default_page_size,\noffset=0,\nbinary_reduction=False,\n):\n\"\"\"Retrieves a list of elements from a URL by iterating over pages until num_elements is obtained.\n    If num_elements is None, then iterates until all elements have been retrieved.\n    If binary_reduction is enabled, errors are assumed to be related to response size. In that case,\n    the page_size is reduced by half until a successful response is obtained or until page_size can't be\n    reduced anymore.\n    Args:\n        url (str): The url to retrieve elements from\n        num_elements (int, optional): The desired number of elements to be retrieved. Defaults to None.\n        page_size (int, optional): Starting page size. Defaults to config.default_page_size.\n        start_limit (int, optional): The starting position for element retrieval. Defaults to 0.\n        binary_reduction (bool, optional): Wether to handle errors by halfing the page size. Defaults to False.\n    Returns:\n        List[dict]: A list of dictionaries representing the retrieved elements.\n    \"\"\"\nel_list = []\nif num_elements is None:\nnum_elements = float(\"inf\")\nwhile len(el_list) < num_elements:\npaginated_url = f\"{url}?limit={page_size}&offset={offset}\"\nres = self.__auth_get(paginated_url)\nif res.status_code != 200:\nif not binary_reduction:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"there was an error retrieving the current list: {details}\"\n)\nlog_response_error(res, warn=True)\ndetails = format_errors_dict(res.json())\nif page_size <= 1:\nraise CommunicationRetrievalError(\nf\"Could not retrieve list. Minimum page size achieved without success: {details}\"\n)\npage_size = page_size // 2\ncontinue\nelse:\ndata = res.json()\nel_list += data[\"results\"]\noffset += len(data[\"results\"])\nif data[\"next\"] is None:\nbreak\nif isinstance(num_elements, int):\nreturn el_list[:num_elements]\nreturn el_list\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.__set_approval_status","title":"__set_approval_status(url, status)","text":"

    Sets the approval status of a resource

    Parameters:

    Name Type Description Default url str

    URL to the resource to update

    required status str

    approval status to set

    required

    Returns:

    Type Description requests.Response

    requests.Response: Response object returned by the update

    Source code in cli/medperf/comms/rest.py
    def __set_approval_status(self, url: str, status: str) -> requests.Response:\n\"\"\"Sets the approval status of a resource\n    Args:\n        url (str): URL to the resource to update\n        status (str): approval status to set\n    Returns:\n        requests.Response: Response object returned by the update\n    \"\"\"\ndata = {\"approval_status\": status}\nres = self.__auth_put(url, json=data)\nreturn res\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.associate_cube","title":"associate_cube(cube_uid, benchmark_uid, metadata={})","text":"

    Create an MLCube-Benchmark association

    Parameters:

    Name Type Description Default cube_uid int

    MLCube UID

    required benchmark_uid int

    Benchmark UID

    required metadata dict

    Additional metadata. Defaults to {}.

    {} Source code in cli/medperf/comms/rest.py
    def associate_cube(self, cube_uid: int, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create an MLCube-Benchmark association\n    Args:\n        cube_uid (int): MLCube UID\n        benchmark_uid (int): Benchmark UID\n        metadata (dict, optional): Additional metadata. Defaults to {}.\n    \"\"\"\ndata = {\n\"approval_status\": Status.PENDING.value,\n\"model_mlcube\": cube_uid,\n\"benchmark\": benchmark_uid,\n\"metadata\": metadata,\n}\nres = self.__auth_post(f\"{self.server_url}/mlcubes/benchmarks/\", json=data)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not associate mlcube to benchmark: {details}\"\n)\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.associate_dset","title":"associate_dset(data_uid, benchmark_uid, metadata={})","text":"

    Create a Dataset Benchmark association

    Parameters:

    Name Type Description Default data_uid int

    Registered dataset UID

    required benchmark_uid int

    Benchmark UID

    required metadata dict

    Additional metadata. Defaults to {}.

    {} Source code in cli/medperf/comms/rest.py
    def associate_dset(self, data_uid: int, benchmark_uid: int, metadata: dict = {}):\n\"\"\"Create a Dataset Benchmark association\n    Args:\n        data_uid (int): Registered dataset UID\n        benchmark_uid (int): Benchmark UID\n        metadata (dict, optional): Additional metadata. Defaults to {}.\n    \"\"\"\ndata = {\n\"dataset\": data_uid,\n\"benchmark\": benchmark_uid,\n\"approval_status\": Status.PENDING.value,\n\"metadata\": metadata,\n}\nres = self.__auth_post(f\"{self.server_url}/datasets/benchmarks/\", json=data)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not associate dataset to benchmark: {details}\"\n)\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_benchmark","title":"get_benchmark(benchmark_uid)","text":"

    Retrieves the benchmark specification file from the server

    Parameters:

    Name Type Description Default benchmark_uid int

    uid for the desired benchmark

    required

    Returns:

    Name Type Description dict dict

    benchmark specification

    Source code in cli/medperf/comms/rest.py
    def get_benchmark(self, benchmark_uid: int) -> dict:\n\"\"\"Retrieves the benchmark specification file from the server\n    Args:\n        benchmark_uid (int): uid for the desired benchmark\n    Returns:\n        dict: benchmark specification\n    \"\"\"\nres = self.__auth_get(f\"{self.server_url}/benchmarks/{benchmark_uid}\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"the specified benchmark doesn't exist: {details}\"\n)\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_benchmark_model_associations","title":"get_benchmark_model_associations(benchmark_uid)","text":"

    Retrieves all the model associations of a benchmark.

    Parameters:

    Name Type Description Default benchmark_uid int

    UID of the desired benchmark

    required

    Returns:

    Type Description List[int]

    list[int]: List of benchmark model associations

    Source code in cli/medperf/comms/rest.py
    def get_benchmark_model_associations(self, benchmark_uid: int) -> List[int]:\n\"\"\"Retrieves all the model associations of a benchmark.\n    Args:\n        benchmark_uid (int): UID of the desired benchmark\n    Returns:\n        list[int]: List of benchmark model associations\n    \"\"\"\nassocs = self.__get_list(f\"{self.server_url}/benchmarks/{benchmark_uid}/models\")\nreturn filter_latest_associations(assocs, \"model_mlcube\")\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_benchmark_results","title":"get_benchmark_results(benchmark_id)","text":"

    Retrieves all results for a given benchmark

    Parameters:

    Name Type Description Default benchmark_id int

    benchmark ID to retrieve results from

    required

    Returns:

    Name Type Description dict dict

    dictionary with the contents of each result in the specified benchmark

    Source code in cli/medperf/comms/rest.py
    def get_benchmark_results(self, benchmark_id: int) -> dict:\n\"\"\"Retrieves all results for a given benchmark\n    Args:\n        benchmark_id (int): benchmark ID to retrieve results from\n    Returns:\n        dict: dictionary with the contents of each result in the specified benchmark\n    \"\"\"\nresults = self.__get_list(\nf\"{self.server_url}/benchmarks/{benchmark_id}/results\"\n)\nreturn results\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_benchmarks","title":"get_benchmarks()","text":"

    Retrieves all benchmarks in the platform.

    Returns:

    Type Description List[dict]

    List[dict]: all benchmarks information.

    Source code in cli/medperf/comms/rest.py
    def get_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks in the platform.\n    Returns:\n        List[dict]: all benchmarks information.\n    \"\"\"\nbmks = self.__get_list(f\"{self.server_url}/benchmarks/\")\nreturn bmks\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_cube_metadata","title":"get_cube_metadata(cube_uid)","text":"

    Retrieves metadata about the specified cube

    Parameters:

    Name Type Description Default cube_uid int

    UID of the desired cube.

    required

    Returns:

    Name Type Description dict dict

    Dictionary containing url and hashes for the cube files

    Source code in cli/medperf/comms/rest.py
    def get_cube_metadata(self, cube_uid: int) -> dict:\n\"\"\"Retrieves metadata about the specified cube\n    Args:\n        cube_uid (int): UID of the desired cube.\n    Returns:\n        dict: Dictionary containing url and hashes for the cube files\n    \"\"\"\nres = self.__auth_get(f\"{self.server_url}/mlcubes/{cube_uid}/\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"the specified cube doesn't exist {details}\"\n)\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_cubes","title":"get_cubes()","text":"

    Retrieves all MLCubes in the platform

    Returns:

    Type Description List[dict]

    List[dict]: List containing the data of all MLCubes

    Source code in cli/medperf/comms/rest.py
    def get_cubes(self) -> List[dict]:\n\"\"\"Retrieves all MLCubes in the platform\n    Returns:\n        List[dict]: List containing the data of all MLCubes\n    \"\"\"\ncubes = self.__get_list(f\"{self.server_url}/mlcubes/\")\nreturn cubes\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_cubes_associations","title":"get_cubes_associations()","text":"

    Get all cube associations related to the current user

    Returns:

    Type Description List[dict]

    List[dict]: List containing all associations information

    Source code in cli/medperf/comms/rest.py
    def get_cubes_associations(self) -> List[dict]:\n\"\"\"Get all cube associations related to the current user\n    Returns:\n        List[dict]: List containing all associations information\n    \"\"\"\nassocs = self.__get_list(f\"{self.server_url}/me/mlcubes/associations/\")\nreturn filter_latest_associations(assocs, \"model_mlcube\")\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_current_user","title":"get_current_user()","text":"

    Retrieve the currently-authenticated user information

    Source code in cli/medperf/comms/rest.py
    def get_current_user(self):\n\"\"\"Retrieve the currently-authenticated user information\"\"\"\nres = self.__auth_get(f\"{self.server_url}/me/\")\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_dataset","title":"get_dataset(dset_uid)","text":"

    Retrieves a specific dataset

    Parameters:

    Name Type Description Default dset_uid int

    Dataset UID

    required

    Returns:

    Name Type Description dict dict

    Dataset metadata

    Source code in cli/medperf/comms/rest.py
    def get_dataset(self, dset_uid: int) -> dict:\n\"\"\"Retrieves a specific dataset\n    Args:\n        dset_uid (int): Dataset UID\n    Returns:\n        dict: Dataset metadata\n    \"\"\"\nres = self.__auth_get(f\"{self.server_url}/datasets/{dset_uid}/\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"Could not retrieve the specified dataset from server: {details}\"\n)\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_datasets","title":"get_datasets()","text":"

    Retrieves all datasets in the platform

    Returns:

    Type Description List[dict]

    List[dict]: List of data from all datasets

    Source code in cli/medperf/comms/rest.py
    def get_datasets(self) -> List[dict]:\n\"\"\"Retrieves all datasets in the platform\n    Returns:\n        List[dict]: List of data from all datasets\n    \"\"\"\ndsets = self.__get_list(f\"{self.server_url}/datasets/\")\nreturn dsets\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_datasets_associations","title":"get_datasets_associations()","text":"

    Get all dataset associations related to the current user

    Returns:

    Type Description List[dict]

    List[dict]: List containing all associations information

    Source code in cli/medperf/comms/rest.py
    def get_datasets_associations(self) -> List[dict]:\n\"\"\"Get all dataset associations related to the current user\n    Returns:\n        List[dict]: List containing all associations information\n    \"\"\"\nassocs = self.__get_list(f\"{self.server_url}/me/datasets/associations/\")\nreturn filter_latest_associations(assocs, \"dataset\")\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_mlcube_datasets","title":"get_mlcube_datasets(mlcube_id)","text":"

    Retrieves all datasets that have the specified mlcube as the prep mlcube

    Parameters:

    Name Type Description Default mlcube_id int

    mlcube ID to retrieve datasets from

    required

    Returns:

    Name Type Description dict dict

    dictionary with the contents of each dataset

    Source code in cli/medperf/comms/rest.py
    def get_mlcube_datasets(self, mlcube_id: int) -> dict:\n\"\"\"Retrieves all datasets that have the specified mlcube as the prep mlcube\n    Args:\n        mlcube_id (int): mlcube ID to retrieve datasets from\n    Returns:\n        dict: dictionary with the contents of each dataset\n    \"\"\"\ndatasets = self.__get_list(f\"{self.server_url}/mlcubes/{mlcube_id}/datasets/\")\nreturn datasets\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_result","title":"get_result(result_uid)","text":"

    Retrieves a specific result data

    Parameters:

    Name Type Description Default result_uid int

    Result UID

    required

    Returns:

    Name Type Description dict dict

    Result metadata

    Source code in cli/medperf/comms/rest.py
    def get_result(self, result_uid: int) -> dict:\n\"\"\"Retrieves a specific result data\n    Args:\n        result_uid (int): Result UID\n    Returns:\n        dict: Result metadata\n    \"\"\"\nres = self.__auth_get(f\"{self.server_url}/results/{result_uid}/\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(\nf\"Could not retrieve the specified result: {details}\"\n)\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_results","title":"get_results()","text":"

    Retrieves all results

    Returns:

    Type Description List[dict]

    List[dict]: List of results

    Source code in cli/medperf/comms/rest.py
    def get_results(self) -> List[dict]:\n\"\"\"Retrieves all results\n    Returns:\n        List[dict]: List of results\n    \"\"\"\nres = self.__get_list(f\"{self.server_url}/results\")\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(f\"Could not retrieve results: {details}\")\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_user_benchmarks","title":"get_user_benchmarks()","text":"

    Retrieves all benchmarks created by the user

    Returns:

    Type Description List[dict]

    List[dict]: Benchmarks data

    Source code in cli/medperf/comms/rest.py
    def get_user_benchmarks(self) -> List[dict]:\n\"\"\"Retrieves all benchmarks created by the user\n    Returns:\n        List[dict]: Benchmarks data\n    \"\"\"\nbmks = self.__get_list(f\"{self.server_url}/me/benchmarks/\")\nreturn bmks\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_user_cubes","title":"get_user_cubes()","text":"

    Retrieves metadata from all cubes registered by the user

    Returns:

    Type Description List[dict]

    List[dict]: List of dictionaries containing the mlcubes registration information

    Source code in cli/medperf/comms/rest.py
    def get_user_cubes(self) -> List[dict]:\n\"\"\"Retrieves metadata from all cubes registered by the user\n    Returns:\n        List[dict]: List of dictionaries containing the mlcubes registration information\n    \"\"\"\ncubes = self.__get_list(f\"{self.server_url}/me/mlcubes/\")\nreturn cubes\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_user_datasets","title":"get_user_datasets()","text":"

    Retrieves all datasets registered by the user

    Returns:

    Name Type Description dict dict

    dictionary with the contents of each dataset registration query

    Source code in cli/medperf/comms/rest.py
    def get_user_datasets(self) -> dict:\n\"\"\"Retrieves all datasets registered by the user\n    Returns:\n        dict: dictionary with the contents of each dataset registration query\n    \"\"\"\ndsets = self.__get_list(f\"{self.server_url}/me/datasets/\")\nreturn dsets\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.get_user_results","title":"get_user_results()","text":"

    Retrieves all results registered by the user

    Returns:

    Name Type Description dict dict

    dictionary with the contents of each result registration query

    Source code in cli/medperf/comms/rest.py
    def get_user_results(self) -> dict:\n\"\"\"Retrieves all results registered by the user\n    Returns:\n        dict: dictionary with the contents of each result registration query\n    \"\"\"\nresults = self.__get_list(f\"{self.server_url}/me/results/\")\nreturn results\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.parse_url","title":"parse_url(url) classmethod","text":"

    Parse the source URL so that it can be used by the comms implementation. It should handle protocols and versioning to be able to communicate with the API.

    Parameters:

    Name Type Description Default url str

    base URL

    required

    Returns:

    Name Type Description str str

    parsed URL with protocol and version

    Source code in cli/medperf/comms/rest.py
    @classmethod\ndef parse_url(cls, url: str) -> str:\n\"\"\"Parse the source URL so that it can be used by the comms implementation.\n    It should handle protocols and versioning to be able to communicate with the API.\n    Args:\n        url (str): base URL\n    Returns:\n        str: parsed URL with protocol and version\n    \"\"\"\nurl_sections = url.split(\"://\")\napi_path = f\"/api/v{config.major_version}\"\n# Remove protocol if passed\nif len(url_sections) > 1:\nurl = \"\".join(url_sections[1:])\nreturn f\"https://{url}{api_path}\"\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.set_dataset_association_approval","title":"set_dataset_association_approval(benchmark_uid, dataset_uid, status)","text":"

    Approves a dataset association

    Parameters:

    Name Type Description Default dataset_uid int

    Dataset UID

    required benchmark_uid int

    Benchmark UID

    required status str

    Approval status to set for the association

    required Source code in cli/medperf/comms/rest.py
    def set_dataset_association_approval(\nself, benchmark_uid: int, dataset_uid: int, status: str\n):\n\"\"\"Approves a dataset association\n    Args:\n        dataset_uid (int): Dataset UID\n        benchmark_uid (int): Benchmark UID\n        status (str): Approval status to set for the association\n    \"\"\"\nurl = f\"{self.server_url}/datasets/{dataset_uid}/benchmarks/{benchmark_uid}/\"\nres = self.__set_approval_status(url, status)\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not approve association between dataset {dataset_uid} and benchmark {benchmark_uid}: {details}\"\n)\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.set_mlcube_association_approval","title":"set_mlcube_association_approval(benchmark_uid, mlcube_uid, status)","text":"

    Approves an mlcube association

    Parameters:

    Name Type Description Default mlcube_uid int

    Dataset UID

    required benchmark_uid int

    Benchmark UID

    required status str

    Approval status to set for the association

    required Source code in cli/medperf/comms/rest.py
    def set_mlcube_association_approval(\nself, benchmark_uid: int, mlcube_uid: int, status: str\n):\n\"\"\"Approves an mlcube association\n    Args:\n        mlcube_uid (int): Dataset UID\n        benchmark_uid (int): Benchmark UID\n        status (str): Approval status to set for the association\n    \"\"\"\nurl = f\"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/\"\nres = self.__set_approval_status(url, status)\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not approve association between mlcube {mlcube_uid} and benchmark {benchmark_uid}: {details}\"\n)\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.set_mlcube_association_priority","title":"set_mlcube_association_priority(benchmark_uid, mlcube_uid, priority)","text":"

    Sets the priority of an mlcube-benchmark association

    Parameters:

    Name Type Description Default mlcube_uid int

    MLCube UID

    required benchmark_uid int

    Benchmark UID

    required priority int

    priority value to set for the association

    required Source code in cli/medperf/comms/rest.py
    def set_mlcube_association_priority(\nself, benchmark_uid: int, mlcube_uid: int, priority: int\n):\n\"\"\"Sets the priority of an mlcube-benchmark association\n    Args:\n        mlcube_uid (int): MLCube UID\n        benchmark_uid (int): Benchmark UID\n        priority (int): priority value to set for the association\n    \"\"\"\nurl = f\"{self.server_url}/mlcubes/{mlcube_uid}/benchmarks/{benchmark_uid}/\"\ndata = {\"priority\": priority}\nres = self.__auth_put(url, json=data)\nif res.status_code != 200:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(\nf\"Could not set the priority of mlcube {mlcube_uid} within the benchmark {benchmark_uid}: {details}\"\n)\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.upload_benchmark","title":"upload_benchmark(benchmark_dict)","text":"

    Uploads a new benchmark to the server.

    Parameters:

    Name Type Description Default benchmark_dict dict

    benchmark_data to be uploaded

    required

    Returns:

    Name Type Description int int

    UID of newly created benchmark

    Source code in cli/medperf/comms/rest.py
    def upload_benchmark(self, benchmark_dict: dict) -> int:\n\"\"\"Uploads a new benchmark to the server.\n    Args:\n        benchmark_dict (dict): benchmark_data to be uploaded\n    Returns:\n        int: UID of newly created benchmark\n    \"\"\"\nres = self.__auth_post(f\"{self.server_url}/benchmarks/\", json=benchmark_dict)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(f\"Could not upload benchmark: {details}\")\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.upload_dataset","title":"upload_dataset(reg_dict)","text":"

    Uploads registration data to the server, under the sha name of the file.

    Parameters:

    Name Type Description Default reg_dict dict

    Dictionary containing registration information.

    required

    Returns:

    Name Type Description int int

    id of the created dataset registration.

    Source code in cli/medperf/comms/rest.py
    def upload_dataset(self, reg_dict: dict) -> int:\n\"\"\"Uploads registration data to the server, under the sha name of the file.\n    Args:\n        reg_dict (dict): Dictionary containing registration information.\n    Returns:\n        int: id of the created dataset registration.\n    \"\"\"\nres = self.__auth_post(f\"{self.server_url}/datasets/\", json=reg_dict)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(f\"Could not upload the dataset: {details}\")\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.upload_mlcube","title":"upload_mlcube(mlcube_body)","text":"

    Uploads an MLCube instance to the platform

    Parameters:

    Name Type Description Default mlcube_body dict

    Dictionary containing all the relevant data for creating mlcubes

    required

    Returns:

    Name Type Description int int

    id of the created mlcube instance on the platform

    Source code in cli/medperf/comms/rest.py
    def upload_mlcube(self, mlcube_body: dict) -> int:\n\"\"\"Uploads an MLCube instance to the platform\n    Args:\n        mlcube_body (dict): Dictionary containing all the relevant data for creating mlcubes\n    Returns:\n        int: id of the created mlcube instance on the platform\n    \"\"\"\nres = self.__auth_post(f\"{self.server_url}/mlcubes/\", json=mlcube_body)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRetrievalError(f\"Could not upload the mlcube: {details}\")\nreturn res.json()\n
    "},{"location":"reference/comms/rest/#comms.rest.REST.upload_result","title":"upload_result(results_dict)","text":"

    Uploads result to the server.

    Parameters:

    Name Type Description Default results_dict dict

    Dictionary containing results information.

    required

    Returns:

    Name Type Description int int

    id of the generated results entry

    Source code in cli/medperf/comms/rest.py
    def upload_result(self, results_dict: dict) -> int:\n\"\"\"Uploads result to the server.\n    Args:\n        results_dict (dict): Dictionary containing results information.\n    Returns:\n        int: id of the generated results entry\n    \"\"\"\nres = self.__auth_post(f\"{self.server_url}/results/\", json=results_dict)\nif res.status_code != 201:\nlog_response_error(res)\ndetails = format_errors_dict(res.json())\nraise CommunicationRequestError(f\"Could not upload the results: {details}\")\nreturn res.json()\n
    "},{"location":"reference/comms/auth/auth0/","title":"Auth0","text":""},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0","title":"Auth0","text":"

    Bases: Auth

    Source code in cli/medperf/comms/auth/auth0.py
    class Auth0(Auth):\ndef __init__(self):\nself.domain = config.auth_domain\nself.client_id = config.auth_client_id\nself.audience = config.auth_audience\nself._lock = threading.Lock()\ndef login(self, email):\n\"\"\"Retrieves and stores an access token/refresh token pair from the auth0\n        backend using the device authorization flow.\n        Args:\n            email (str): user email. This will be used to validate that the received\n                         id_token contains the same email address.\n        \"\"\"\ndevice_code_response = self.__request_device_code()\ndevice_code = device_code_response[\"device_code\"]\nuser_code = device_code_response[\"user_code\"]\nverification_uri_complete = device_code_response[\"verification_uri_complete\"]\ninterval = device_code_response[\"interval\"]\nconfig.ui.print(\n\"\\nPlease go to the following link to complete your login request:\\n\"\nf\"\\t{verification_uri_complete}\\n\\n\"\n\"Make sure that you will be presented with the following code:\\n\"\nf\"\\t{user_code}\\n\\n\"\n)\nconfig.ui.print_warning(\n\"Keep this terminal open until you complete your login request. \"\n\"The command will exit on its own once you complete the request. \"\n\"If you wish to stop the login request anyway, press Ctrl+C.\"\n)\ntoken_response, token_issued_at = self.__get_device_access_token(\ndevice_code, interval\n)\naccess_token = token_response[\"access_token\"]\nid_token = token_response[\"id_token\"]\nrefresh_token = token_response[\"refresh_token\"]\ntoken_expires_in = token_response[\"expires_in\"]\nid_token_payload = verify_token(id_token)\nself.__check_token_email(id_token_payload, email)\nset_credentials(\naccess_token,\nrefresh_token,\nid_token_payload,\ntoken_issued_at,\ntoken_expires_in,\n)\ndef __request_device_code(self):\n\"\"\"Get a device code from the auth0 backend to be used for the authorization process\"\"\"\nurl = f\"https://{self.domain}/oauth/device/code\"\nheaders = {\"content-type\": \"application/x-www-form-urlencoded\"}\nbody = {\n\"client_id\": self.client_id,\n\"audience\": self.audience,\n\"scope\": \"offline_access openid email\",\n}\nres = requests.post(url=url, headers=headers, data=body)\nif res.status_code != 200:\nself.__raise_errors(res, \"Login\")\nreturn res.json()\ndef __get_device_access_token(self, device_code, polling_interval):\n\"\"\"Get the access token from the auth0 backend associated with\n        the device code requested before. This function will keep polling\n        the access token until the user completes the browser flow part\n        of the authorization process.\n        Args:\n            device_code (str): A temporary device code requested by `__request_device_code`\n            polling_interval (float): number of seconds to wait between each two polling requests\n        Returns:\n            json_res (dict): the response of the successful request, containg the access/refresh tokens pair\n            token_issued_at (float): the timestamp when the access token was issued\n        \"\"\"\nurl = f\"https://{self.domain}/oauth/token\"\nheaders = {\"content-type\": \"application/x-www-form-urlencoded\"}\nbody = {\n\"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n\"device_code\": device_code,\n\"client_id\": self.client_id,\n}\nwhile True:\ntime.sleep(polling_interval)\ntoken_issued_at = time.time()\nres = requests.post(url=url, headers=headers, data=body)\nif res.status_code == 200:\njson_res = res.json()\nreturn json_res, token_issued_at\ntry:\njson_res = res.json()\nexcept requests.exceptions.JSONDecodeError:\njson_res = {}\nerror = json_res.get(\"error\", None)\nif error not in [\"slow_down\", \"authorization_pending\"]:\nself.__raise_errors(res, \"Login\")\ndef __check_token_email(self, id_token_payload, email):\n\"\"\"Checks if the email provided by the user in the terminal matches the\n        email found in the recieved id token.\"\"\"\nemail_in_token = id_token_payload[\"email\"]\nif email.lower() != email_in_token:\nraise CommunicationError(\n\"The email provided in the terminal does not match the one provided during login\"\n)\ndef logout(self):\n\"\"\"Logs out the user by revoking their refresh token and deleting the\n        stored tokens.\"\"\"\ncreds = read_credentials()\nrefresh_token = creds[\"refresh_token\"]\nurl = f\"https://{self.domain}/oauth/revoke\"\nheaders = {\"content-type\": \"application/json\"}\nbody = {\n\"client_id\": self.client_id,\n\"token\": refresh_token,\n}\nres = requests.post(url=url, headers=headers, json=body)\nif res.status_code != 200:\nself.__raise_errors(res, \"Logout\")\ndelete_credentials()\n@property\ndef access_token(self):\n\"\"\"Thread and process-safe access token retrieval\"\"\"\n# In case of multiple threads are using the same connection object,\n# keep the thread lock, otherwise the database will throw\n# errors of starting a transaction within a transaction.\n# In case of each thread is using a different connection object,\n# keep the thread lock to avoid the OperationalError when\n# multiple threads want to access the database.\nwith self._lock:\n# TODO: This is temporary. Use a cleaner solution.\ndb = sqlite3.connect(config.tokens_db, isolation_level=None, timeout=60)\ntry:\ndb.execute(\"BEGIN EXCLUSIVE TRANSACTION\")\nexcept sqlite3.OperationalError:\nmsg = \"Another process is using the database. Try again later\"\nraise CommunicationError(msg)\ntoken = self._access_token\n# Sqlite will automatically execute COMMIT and close the connection\n# if an exception is raised during the retrieval of the access token.\ndb.execute(\"COMMIT\")\ndb.close()\nreturn token\n@property\ndef _access_token(self):\n\"\"\"Reads and returns an access token of the currently logged\n        in user to be used for authorizing requests to the MedPerf server.\n        Refresh the token if necessary.\n        Returns:\n            access_token (str): the access token\n        \"\"\"\ncreds = read_credentials()\naccess_token = creds[\"access_token\"]\nrefresh_token = creds[\"refresh_token\"]\ntoken_expires_in = creds[\"token_expires_in\"]\ntoken_issued_at = creds[\"token_issued_at\"]\nif (\ntime.time()\n> token_issued_at + token_expires_in - config.token_expiration_leeway\n):\naccess_token = self.__refresh_access_token(refresh_token)\nreturn access_token\ndef __refresh_access_token(self, refresh_token):\n\"\"\"Retrieve and store a new access token using a refresh token.\n        A new refresh token will also be retrieved and stored.\n        Args:\n            refresh_token (str): the refresh token\n        Returns:\n            access_token (str): the new access token\n        \"\"\"\nurl = f\"https://{self.domain}/oauth/token\"\nheaders = {\"content-type\": \"application/x-www-form-urlencoded\"}\nbody = {\n\"grant_type\": \"refresh_token\",\n\"client_id\": self.client_id,\n\"refresh_token\": refresh_token,\n}\ntoken_issued_at = time.time()\nlogging.debug(\"Refreshing access token.\")\nres = requests.post(url=url, headers=headers, data=body)\nif res.status_code != 200:\nself.__raise_errors(res, \"Token refresh\")\njson_res = res.json()\naccess_token = json_res[\"access_token\"]\nid_token = json_res[\"id_token\"]\nrefresh_token = json_res[\"refresh_token\"]\ntoken_expires_in = json_res[\"expires_in\"]\nid_token_payload = verify_token(id_token)\nset_credentials(\naccess_token,\nrefresh_token,\nid_token_payload,\ntoken_issued_at,\ntoken_expires_in,\n)\nreturn access_token\ndef __raise_errors(self, res, action):\n\"\"\"log the failed request's response and raise errors.\n        Args:\n            res (requests.Response): the response of a failed request\n            action (str): a string for more informative error display\n            to the user.\n        \"\"\"\nlog_response_error(res)\nif res.status_code == 429:\nraise CommunicationError(\"Too many requests. Try again later.\")\ntry:\njson_res = res.json()\nexcept requests.exceptions.JSONDecodeError:\njson_res = {}\ndescription = json_res.get(\"error_description\", \"\")\nmsg = f\"{action} failed.\"\nif description:\nmsg += f\" {description}\"\nraise CommunicationError(msg)\n
    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.access_token","title":"access_token property","text":"

    Thread and process-safe access token retrieval

    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.__check_token_email","title":"__check_token_email(id_token_payload, email)","text":"

    Checks if the email provided by the user in the terminal matches the email found in the recieved id token.

    Source code in cli/medperf/comms/auth/auth0.py
    def __check_token_email(self, id_token_payload, email):\n\"\"\"Checks if the email provided by the user in the terminal matches the\n    email found in the recieved id token.\"\"\"\nemail_in_token = id_token_payload[\"email\"]\nif email.lower() != email_in_token:\nraise CommunicationError(\n\"The email provided in the terminal does not match the one provided during login\"\n)\n
    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.__get_device_access_token","title":"__get_device_access_token(device_code, polling_interval)","text":"

    Get the access token from the auth0 backend associated with the device code requested before. This function will keep polling the access token until the user completes the browser flow part of the authorization process.

    Parameters:

    Name Type Description Default device_code str

    A temporary device code requested by __request_device_code

    required polling_interval float

    number of seconds to wait between each two polling requests

    required

    Returns:

    Name Type Description json_res dict

    the response of the successful request, containg the access/refresh tokens pair

    token_issued_at float

    the timestamp when the access token was issued

    Source code in cli/medperf/comms/auth/auth0.py
    def __get_device_access_token(self, device_code, polling_interval):\n\"\"\"Get the access token from the auth0 backend associated with\n    the device code requested before. This function will keep polling\n    the access token until the user completes the browser flow part\n    of the authorization process.\n    Args:\n        device_code (str): A temporary device code requested by `__request_device_code`\n        polling_interval (float): number of seconds to wait between each two polling requests\n    Returns:\n        json_res (dict): the response of the successful request, containg the access/refresh tokens pair\n        token_issued_at (float): the timestamp when the access token was issued\n    \"\"\"\nurl = f\"https://{self.domain}/oauth/token\"\nheaders = {\"content-type\": \"application/x-www-form-urlencoded\"}\nbody = {\n\"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n\"device_code\": device_code,\n\"client_id\": self.client_id,\n}\nwhile True:\ntime.sleep(polling_interval)\ntoken_issued_at = time.time()\nres = requests.post(url=url, headers=headers, data=body)\nif res.status_code == 200:\njson_res = res.json()\nreturn json_res, token_issued_at\ntry:\njson_res = res.json()\nexcept requests.exceptions.JSONDecodeError:\njson_res = {}\nerror = json_res.get(\"error\", None)\nif error not in [\"slow_down\", \"authorization_pending\"]:\nself.__raise_errors(res, \"Login\")\n
    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.__raise_errors","title":"__raise_errors(res, action)","text":"

    log the failed request's response and raise errors.

    Parameters:

    Name Type Description Default res requests.Response

    the response of a failed request

    required action str

    a string for more informative error display

    required Source code in cli/medperf/comms/auth/auth0.py
    def __raise_errors(self, res, action):\n\"\"\"log the failed request's response and raise errors.\n    Args:\n        res (requests.Response): the response of a failed request\n        action (str): a string for more informative error display\n        to the user.\n    \"\"\"\nlog_response_error(res)\nif res.status_code == 429:\nraise CommunicationError(\"Too many requests. Try again later.\")\ntry:\njson_res = res.json()\nexcept requests.exceptions.JSONDecodeError:\njson_res = {}\ndescription = json_res.get(\"error_description\", \"\")\nmsg = f\"{action} failed.\"\nif description:\nmsg += f\" {description}\"\nraise CommunicationError(msg)\n
    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.__refresh_access_token","title":"__refresh_access_token(refresh_token)","text":"

    Retrieve and store a new access token using a refresh token. A new refresh token will also be retrieved and stored.

    Parameters:

    Name Type Description Default refresh_token str

    the refresh token

    required

    Returns:

    Name Type Description access_token str

    the new access token

    Source code in cli/medperf/comms/auth/auth0.py
    def __refresh_access_token(self, refresh_token):\n\"\"\"Retrieve and store a new access token using a refresh token.\n    A new refresh token will also be retrieved and stored.\n    Args:\n        refresh_token (str): the refresh token\n    Returns:\n        access_token (str): the new access token\n    \"\"\"\nurl = f\"https://{self.domain}/oauth/token\"\nheaders = {\"content-type\": \"application/x-www-form-urlencoded\"}\nbody = {\n\"grant_type\": \"refresh_token\",\n\"client_id\": self.client_id,\n\"refresh_token\": refresh_token,\n}\ntoken_issued_at = time.time()\nlogging.debug(\"Refreshing access token.\")\nres = requests.post(url=url, headers=headers, data=body)\nif res.status_code != 200:\nself.__raise_errors(res, \"Token refresh\")\njson_res = res.json()\naccess_token = json_res[\"access_token\"]\nid_token = json_res[\"id_token\"]\nrefresh_token = json_res[\"refresh_token\"]\ntoken_expires_in = json_res[\"expires_in\"]\nid_token_payload = verify_token(id_token)\nset_credentials(\naccess_token,\nrefresh_token,\nid_token_payload,\ntoken_issued_at,\ntoken_expires_in,\n)\nreturn access_token\n
    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.__request_device_code","title":"__request_device_code()","text":"

    Get a device code from the auth0 backend to be used for the authorization process

    Source code in cli/medperf/comms/auth/auth0.py
    def __request_device_code(self):\n\"\"\"Get a device code from the auth0 backend to be used for the authorization process\"\"\"\nurl = f\"https://{self.domain}/oauth/device/code\"\nheaders = {\"content-type\": \"application/x-www-form-urlencoded\"}\nbody = {\n\"client_id\": self.client_id,\n\"audience\": self.audience,\n\"scope\": \"offline_access openid email\",\n}\nres = requests.post(url=url, headers=headers, data=body)\nif res.status_code != 200:\nself.__raise_errors(res, \"Login\")\nreturn res.json()\n
    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.login","title":"login(email)","text":"

    Retrieves and stores an access token/refresh token pair from the auth0 backend using the device authorization flow.

    Parameters:

    Name Type Description Default email str

    user email. This will be used to validate that the received id_token contains the same email address.

    required Source code in cli/medperf/comms/auth/auth0.py
    def login(self, email):\n\"\"\"Retrieves and stores an access token/refresh token pair from the auth0\n    backend using the device authorization flow.\n    Args:\n        email (str): user email. This will be used to validate that the received\n                     id_token contains the same email address.\n    \"\"\"\ndevice_code_response = self.__request_device_code()\ndevice_code = device_code_response[\"device_code\"]\nuser_code = device_code_response[\"user_code\"]\nverification_uri_complete = device_code_response[\"verification_uri_complete\"]\ninterval = device_code_response[\"interval\"]\nconfig.ui.print(\n\"\\nPlease go to the following link to complete your login request:\\n\"\nf\"\\t{verification_uri_complete}\\n\\n\"\n\"Make sure that you will be presented with the following code:\\n\"\nf\"\\t{user_code}\\n\\n\"\n)\nconfig.ui.print_warning(\n\"Keep this terminal open until you complete your login request. \"\n\"The command will exit on its own once you complete the request. \"\n\"If you wish to stop the login request anyway, press Ctrl+C.\"\n)\ntoken_response, token_issued_at = self.__get_device_access_token(\ndevice_code, interval\n)\naccess_token = token_response[\"access_token\"]\nid_token = token_response[\"id_token\"]\nrefresh_token = token_response[\"refresh_token\"]\ntoken_expires_in = token_response[\"expires_in\"]\nid_token_payload = verify_token(id_token)\nself.__check_token_email(id_token_payload, email)\nset_credentials(\naccess_token,\nrefresh_token,\nid_token_payload,\ntoken_issued_at,\ntoken_expires_in,\n)\n
    "},{"location":"reference/comms/auth/auth0/#comms.auth.auth0.Auth0.logout","title":"logout()","text":"

    Logs out the user by revoking their refresh token and deleting the stored tokens.

    Source code in cli/medperf/comms/auth/auth0.py
    def logout(self):\n\"\"\"Logs out the user by revoking their refresh token and deleting the\n    stored tokens.\"\"\"\ncreds = read_credentials()\nrefresh_token = creds[\"refresh_token\"]\nurl = f\"https://{self.domain}/oauth/revoke\"\nheaders = {\"content-type\": \"application/json\"}\nbody = {\n\"client_id\": self.client_id,\n\"token\": refresh_token,\n}\nres = requests.post(url=url, headers=headers, json=body)\nif res.status_code != 200:\nself.__raise_errors(res, \"Logout\")\ndelete_credentials()\n
    "},{"location":"reference/comms/auth/interface/","title":"Interface","text":""},{"location":"reference/comms/auth/interface/#comms.auth.interface.Auth","title":"Auth","text":"

    Bases: ABC

    Source code in cli/medperf/comms/auth/interface.py
    class Auth(ABC):\n@abstractmethod\ndef __init__(self):\n\"\"\"Initialize the class\"\"\"\n@abstractmethod\ndef login(self, email):\n\"\"\"Log in a user\"\"\"\n@abstractmethod\ndef logout(self):\n\"\"\"Log out a user\"\"\"\n@property\n@abstractmethod\ndef access_token(self):\n\"\"\"An access token to authorize requests to the MedPerf server\"\"\"\n
    "},{"location":"reference/comms/auth/interface/#comms.auth.interface.Auth.access_token","title":"access_token abstractmethod property","text":"

    An access token to authorize requests to the MedPerf server

    "},{"location":"reference/comms/auth/interface/#comms.auth.interface.Auth.__init__","title":"__init__() abstractmethod","text":"

    Initialize the class

    Source code in cli/medperf/comms/auth/interface.py
    @abstractmethod\ndef __init__(self):\n\"\"\"Initialize the class\"\"\"\n
    "},{"location":"reference/comms/auth/interface/#comms.auth.interface.Auth.login","title":"login(email) abstractmethod","text":"

    Log in a user

    Source code in cli/medperf/comms/auth/interface.py
    @abstractmethod\ndef login(self, email):\n\"\"\"Log in a user\"\"\"\n
    "},{"location":"reference/comms/auth/interface/#comms.auth.interface.Auth.logout","title":"logout() abstractmethod","text":"

    Log out a user

    Source code in cli/medperf/comms/auth/interface.py
    @abstractmethod\ndef logout(self):\n\"\"\"Log out a user\"\"\"\n
    "},{"location":"reference/comms/auth/local/","title":"Local","text":""},{"location":"reference/comms/auth/local/#comms.auth.local.Local","title":"Local","text":"

    Bases: Auth

    Source code in cli/medperf/comms/auth/local.py
    class Local(Auth):\ndef __init__(self):\nwith open(config.local_tokens_path) as f:\nself.tokens = json.load(f)\ndef login(self, email):\n\"\"\"Retrieves and stores an access token from a local store json file.\n        Args:\n            email (str): user email.\n        \"\"\"\ntry:\naccess_token = self.tokens[email]\nexcept KeyError:\nraise InvalidArgumentError(\n\"The provided email does not exist for testing. \"\n\"Make sure you activated the right profile.\"\n)\nrefresh_token = \"refresh token\"\nid_token_payload = {\"email\": email}\ntoken_issued_at = 0\ntoken_expires_in = 10**10\nset_credentials(\naccess_token,\nrefresh_token,\nid_token_payload,\ntoken_issued_at,\ntoken_expires_in,\n)\ndef logout(self):\n\"\"\"Logs out the user by deleting the stored tokens.\"\"\"\ndelete_credentials()\n@property\ndef access_token(self):\n\"\"\"Reads and returns an access token of the currently logged\n        in user to be used for authorizing requests to the MedPerf server.\n        Returns:\n            access_token (str): the access token\n        \"\"\"\ncreds = read_credentials()\naccess_token = creds[\"access_token\"]\nreturn access_token\n
    "},{"location":"reference/comms/auth/local/#comms.auth.local.Local.access_token","title":"access_token property","text":"

    Reads and returns an access token of the currently logged in user to be used for authorizing requests to the MedPerf server.

    Returns:

    Name Type Description access_token str

    the access token

    "},{"location":"reference/comms/auth/local/#comms.auth.local.Local.login","title":"login(email)","text":"

    Retrieves and stores an access token from a local store json file.

    Parameters:

    Name Type Description Default email str

    user email.

    required Source code in cli/medperf/comms/auth/local.py
    def login(self, email):\n\"\"\"Retrieves and stores an access token from a local store json file.\n    Args:\n        email (str): user email.\n    \"\"\"\ntry:\naccess_token = self.tokens[email]\nexcept KeyError:\nraise InvalidArgumentError(\n\"The provided email does not exist for testing. \"\n\"Make sure you activated the right profile.\"\n)\nrefresh_token = \"refresh token\"\nid_token_payload = {\"email\": email}\ntoken_issued_at = 0\ntoken_expires_in = 10**10\nset_credentials(\naccess_token,\nrefresh_token,\nid_token_payload,\ntoken_issued_at,\ntoken_expires_in,\n)\n
    "},{"location":"reference/comms/auth/local/#comms.auth.local.Local.logout","title":"logout()","text":"

    Logs out the user by deleting the stored tokens.

    Source code in cli/medperf/comms/auth/local.py
    def logout(self):\n\"\"\"Logs out the user by deleting the stored tokens.\"\"\"\ndelete_credentials()\n
    "},{"location":"reference/comms/auth/token_verifier/","title":"Token verifier","text":"

    This module defines a wrapper around the existing token verifier in auth0-python library. The library is designed to cache public keys in memory. Since our client is ephemeral, we wrapped the library's JwksFetcher to cache keys in the filesystem storage, and wrapped the library's signature verifier to use this new JwksFetcher

    "},{"location":"reference/comms/entity_resources/resources/","title":"Resources","text":"

    This module downloads files from the internet. It provides a set of functions to download common files that are necessary for workflow executions and are not on the MedPerf server. An example of such files is model weights of a Model MLCube.

    This module takes care of validating the integrity of the downloaded file if a hash was specified when requesting the file. It also returns the hash of the downloaded file, which can be the original specified hash or the calculated hash of the freshly downloaded file if no hash was specified.

    Additionally, to avoid unnecessary downloads, an existing file will not be re-downloaded.

    "},{"location":"reference/comms/entity_resources/resources/#comms.entity_resources.resources.get_benchmark_demo_dataset","title":"get_benchmark_demo_dataset(url, expected_hash=None)","text":"

    Downloads and extracts a demo dataset. If the hash is provided, the file's integrity will be checked upon download.

    Parameters:

    Name Type Description Default url str

    URL where the compressed demo dataset file can be downloaded.

    required expected_hash str

    expected hash of the downloaded file

    None

    Returns:

    Name Type Description output_path str

    location where the uncompressed demo dataset is stored locally.

    hash_value str

    The hash of the downloaded tarball file

    Source code in cli/medperf/comms/entity_resources/resources.py
    def get_benchmark_demo_dataset(url: str, expected_hash: str = None) -> str:\n\"\"\"Downloads and extracts a demo dataset. If the hash is provided,\n    the file's integrity will be checked upon download.\n    Args:\n        url (str): URL where the compressed demo dataset file can be downloaded.\n        expected_hash (str, optional): expected hash of the downloaded file\n    Returns:\n        output_path (str): location where the uncompressed demo dataset is stored locally.\n        hash_value (str): The hash of the downloaded tarball file\n    \"\"\"\n# TODO: at some point maybe it is better to download demo datasets in\n# their benchmark folder. Doing this, we should then modify\n# the compatibility test command and remove the option of directly passing\n# demo datasets. This would look cleaner.\n# Possible cons: if multiple benchmarks use the same demo dataset.\ndemo_storage = config.demo_datasets_folder\nif expected_hash:\n# If the folder exists, return\ndemo_dataset_folder = os.path.join(demo_storage, expected_hash)\nif os.path.exists(demo_dataset_folder):\nreturn demo_dataset_folder, expected_hash\n# make sure files are uncompressed while in tmp storage, to avoid any clutter\n# objects if uncompression fails for some reason.\ntmp_output_folder = generate_tmp_path()\noutput_tarball_path = os.path.join(tmp_output_folder, config.tarball_filename)\nhash_value = download_resource(url, output_tarball_path, expected_hash)\nuntar(output_tarball_path)\ndemo_dataset_folder = os.path.join(demo_storage, hash_value)\nif os.path.exists(demo_dataset_folder):\n# handle the possibility of having clutter uncompressed files\nremove_path(demo_dataset_folder)\nos.rename(tmp_output_folder, demo_dataset_folder)\nreturn demo_dataset_folder, hash_value\n
    "},{"location":"reference/comms/entity_resources/resources/#comms.entity_resources.resources.get_cube","title":"get_cube(url, cube_path, expected_hash=None)","text":"

    Downloads and writes a cube mlcube.yaml file

    Source code in cli/medperf/comms/entity_resources/resources.py
    def get_cube(url: str, cube_path: str, expected_hash: str = None):\n\"\"\"Downloads and writes a cube mlcube.yaml file\"\"\"\noutput_path = os.path.join(cube_path, config.cube_filename)\nreturn _get_regular_file(url, output_path, expected_hash)\n
    "},{"location":"reference/comms/entity_resources/resources/#comms.entity_resources.resources.get_cube_additional","title":"get_cube_additional(url, cube_path, expected_tarball_hash=None)","text":"

    Retrieves additional files of an MLCube. The additional files will be in a compressed tarball file. The function will additionally extract this file.

    Parameters:

    Name Type Description Default url str

    URL where the additional_files.tar.gz file can be downloaded.

    required cube_path str

    Cube location.

    required expected_tarball_hash str

    expected hash of tarball file

    None

    Returns:

    Name Type Description tarball_hash str

    The hash of the downloaded tarball file

    Source code in cli/medperf/comms/entity_resources/resources.py
    def get_cube_additional(\nurl: str,\ncube_path: str,\nexpected_tarball_hash: str = None,\n) -> str:\n\"\"\"Retrieves additional files of an MLCube. The additional files\n    will be in a compressed tarball file. The function will additionally\n    extract this file.\n    Args:\n        url (str): URL where the additional_files.tar.gz file can be downloaded.\n        cube_path (str): Cube location.\n        expected_tarball_hash (str, optional): expected hash of tarball file\n    Returns:\n        tarball_hash (str): The hash of the downloaded tarball file\n    \"\"\"\nadditional_files_folder = os.path.join(cube_path, config.additional_path)\nmlcube_cache_file = os.path.join(cube_path, config.mlcube_cache_file)\nif not _should_get_cube_additional(\nadditional_files_folder, expected_tarball_hash, mlcube_cache_file\n):\nreturn expected_tarball_hash\n# Download the additional files. Make sure files are extracted in tmp storage\n# to avoid any clutter objects if uncompression fails for some reason.\ntmp_output_folder = generate_tmp_path()\noutput_tarball_path = os.path.join(tmp_output_folder, config.tarball_filename)\ntarball_hash = download_resource(url, output_tarball_path, expected_tarball_hash)\nuntar(output_tarball_path)\nparent_folder = os.path.dirname(os.path.normpath(additional_files_folder))\nos.makedirs(parent_folder, exist_ok=True)\nif os.path.exists(additional_files_folder):\n# handle the possibility of having clutter uncompressed files\nremove_path(additional_files_folder)\nos.rename(tmp_output_folder, additional_files_folder)\n# Store the downloaded tarball hash to be used later for verifying that the\n# local cache is up to date\nwith open(mlcube_cache_file, \"w\") as f:  # assumes parent folder already exists\ncontents = {\"additional_files_cached_hash\": tarball_hash}\nyaml.dump(contents, f)\nreturn tarball_hash\n
    "},{"location":"reference/comms/entity_resources/resources/#comms.entity_resources.resources.get_cube_image","title":"get_cube_image(url, cube_path, hash_value=None)","text":"

    Retrieves and stores the image file from the server. Stores images on a shared location, and retrieves a cached image by hash if found locally. Creates a symbolic link to the cube storage.

    Parameters:

    Name Type Description Default url str

    URL where the image file can be downloaded.

    required cube_path str

    Path to cube.

    required hash_value (str, Optional)

    File hash to store under shared storage. Defaults to None.

    None

    Returns:

    Name Type Description image_cube_file str

    Location where the image file is stored locally.

    hash_value str

    The hash of the downloaded file

    Source code in cli/medperf/comms/entity_resources/resources.py
    def get_cube_image(url: str, cube_path: str, hash_value: str = None) -> str:\n\"\"\"Retrieves and stores the image file from the server. Stores images\n    on a shared location, and retrieves a cached image by hash if found locally.\n    Creates a symbolic link to the cube storage.\n    Args:\n        url (str): URL where the image file can be downloaded.\n        cube_path (str): Path to cube.\n        hash_value (str, Optional): File hash to store under shared storage. Defaults to None.\n    Returns:\n        image_cube_file: Location where the image file is stored locally.\n        hash_value (str): The hash of the downloaded file\n    \"\"\"\nimage_path = config.image_path\nimage_name = get_cube_image_name(cube_path)\nimage_cube_path = os.path.join(cube_path, image_path)\nos.makedirs(image_cube_path, exist_ok=True)\nimage_cube_file = os.path.join(image_cube_path, image_name)\nif os.path.islink(image_cube_file):  # could be a broken link\n# Remove existing links\nos.unlink(image_cube_file)\nimgs_storage = config.images_folder\nif not hash_value:\n# No hash provided, we need to download the file first\ntmp_output_path = generate_tmp_path()\nhash_value = download_resource(url, tmp_output_path)\nimg_storage = os.path.join(imgs_storage, hash_value)\nshutil.move(tmp_output_path, img_storage)\nelse:\nimg_storage = os.path.join(imgs_storage, hash_value)\nif not os.path.exists(img_storage):\n# If image doesn't exist locally, download it normally\ndownload_resource(url, img_storage, hash_value)\n# Create a symbolic link to individual cube storage\nos.symlink(img_storage, image_cube_file)\nreturn image_cube_file, hash_value\n
    "},{"location":"reference/comms/entity_resources/resources/#comms.entity_resources.resources.get_cube_params","title":"get_cube_params(url, cube_path, expected_hash=None)","text":"

    Downloads and writes a cube parameters.yaml file

    Source code in cli/medperf/comms/entity_resources/resources.py
    def get_cube_params(url: str, cube_path: str, expected_hash: str = None):\n\"\"\"Downloads and writes a cube parameters.yaml file\"\"\"\noutput_path = os.path.join(cube_path, config.workspace_path, config.params_filename)\nreturn _get_regular_file(url, output_path, expected_hash)\n
    "},{"location":"reference/comms/entity_resources/utils/","title":"Utils","text":""},{"location":"reference/comms/entity_resources/utils/#comms.entity_resources.utils.__parse_resource","title":"__parse_resource(resource)","text":"

    Parses a resource string and returns its identifier and the source class it can be downloaded from. The function iterates over all supported sources and checks which one accepts this resource. A resource is a string that should match a certain pattern to be downloaded by a certain resource.

    If the resource pattern does not correspond to any supported source, the function raises an InvalidArgumentError

    Parameters:

    Name Type Description Default resource str

    The resource string. Must be in the form : required Source code in cli/medperf/comms/entity_resources/utils.py

    def __parse_resource(resource: str):\n\"\"\"Parses a resource string and returns its identifier and the source class\n    it can be downloaded from.\n    The function iterates over all supported sources and checks which one accepts\n    this resource. A resource is a string that should match a certain pattern to be\n    downloaded by a certain resource.\n    If the resource pattern does not correspond to any supported source, the\n    function raises an `InvalidArgumentError`\n    Args:\n        resource (str): The resource string. Must be in the form <source_prefix>:<resource_identifier>\n        or a url. The later case will be interpreted as a direct download link.\n    \"\"\"\nfor source_class in supported_sources:\nresource_identifier = source_class.validate_resource(resource)\nif resource_identifier:\nreturn source_class, resource_identifier\n# In this case the input format is not compatible with any source\nmsg = f\"\"\"Invalid resource input: {resource}. A Resource must be a url or\n    in the following format: '<source_prefix>:<resource_identifier>'. Run\n    `medperf mlcube submit --help` for more details.\"\"\"\nraise InvalidArgumentError(msg)\n
    "},{"location":"reference/comms/entity_resources/utils/#comms.entity_resources.utils.download_resource","title":"download_resource(resource, output_path, expected_hash=None)","text":"

    Downloads a resource/file from the internet. Passing a hash is optional. If hash is provided, the downloaded file's hash will be checked and an error will be raised if it is incorrect.

    Upon success, the function returns the hash of the downloaded file.

    Parameters:

    Name Type Description Default resource str

    The resource string. Must be in the form : required output_path str

    The path to download the resource to

    required expected_hash (optional, str)

    The expected hash of the file to be downloaded

    None

    Returns:

    Type Description

    The hash of the downloaded file (or existing file)

    Source code in cli/medperf/comms/entity_resources/utils.py
    def download_resource(\nresource: str, output_path: str, expected_hash: Optional[str] = None\n):\n\"\"\"Downloads a resource/file from the internet. Passing a hash is optional.\n    If hash is provided, the downloaded file's hash will be checked and an error\n    will be raised if it is incorrect.\n    Upon success, the function returns the hash of the downloaded file.\n    Args:\n        resource (str): The resource string. Must be in the form <source_prefix>:<resource_identifier>\n        or a url.\n        output_path (str): The path to download the resource to\n        expected_hash (optional, str): The expected hash of the file to be downloaded\n    Returns:\n        The hash of the downloaded file (or existing file)\n    \"\"\"\ntmp_output_path = tmp_download_resource(resource)\ncalculated_hash = get_file_hash(tmp_output_path)\nif expected_hash and calculated_hash != expected_hash:\nlogging.debug(f\"{resource}: Expected {expected_hash}, found {calculated_hash}.\")\nraise InvalidEntityError(f\"Hash mismatch: {resource}\")\nto_permanent_path(tmp_output_path, output_path)\nreturn calculated_hash\n
    "},{"location":"reference/comms/entity_resources/utils/#comms.entity_resources.utils.tmp_download_resource","title":"tmp_download_resource(resource)","text":"

    Downloads a resource to the temporary storage.

    Parameters:

    Name Type Description Default resource str

    The resource string. Must be in the form : required

    Returns:

    Name Type Description tmp_output_path str

    The location where the resource was downloaded

    Source code in cli/medperf/comms/entity_resources/utils.py
    def tmp_download_resource(resource):\n\"\"\"Downloads a resource to the temporary storage.\n    Args:\n        resource (str): The resource string. Must be in the form <source_prefix>:<resource_identifier>\n        or a url.\n    Returns:\n        tmp_output_path (str): The location where the resource was downloaded\n    \"\"\"\ntmp_output_path = generate_tmp_path()\nsource_class, resource_identifier = __parse_resource(resource)\nsource = source_class()\nsource.authenticate()\nsource.download(resource_identifier, tmp_output_path)\nreturn tmp_output_path\n
    "},{"location":"reference/comms/entity_resources/utils/#comms.entity_resources.utils.to_permanent_path","title":"to_permanent_path(tmp_output_path, output_path)","text":"

    Writes a file from the temporary storage to the desired output path.

    Source code in cli/medperf/comms/entity_resources/utils.py
    def to_permanent_path(tmp_output_path, output_path):\n\"\"\"Writes a file from the temporary storage to the desired output path.\"\"\"\noutput_folder = os.path.dirname(os.path.abspath(output_path))\nos.makedirs(output_folder, exist_ok=True)\nos.rename(tmp_output_path, output_path)\n
    "},{"location":"reference/comms/entity_resources/sources/direct/","title":"Direct","text":""},{"location":"reference/comms/entity_resources/sources/direct/#comms.entity_resources.sources.direct.DirectLinkSource","title":"DirectLinkSource","text":"

    Bases: BaseSource

    Source code in cli/medperf/comms/entity_resources/sources/direct.py
    class DirectLinkSource(BaseSource):\nprefix = \"direct:\"\n@classmethod\ndef validate_resource(cls, value: str):\n\"\"\"This class expects a resource string of the form\n        `direct:<URL>` or only a URL.\n        Args:\n            resource (str): the resource string\n        Returns:\n            (str|None): The URL if the pattern matches, else None\n        \"\"\"\nprefix = cls.prefix\nif value.startswith(prefix):\nprefix_len = len(prefix)\nvalue = value[prefix_len:]\nif validators.url(value):\nreturn value\ndef __init__(self):\npass\ndef authenticate(self):\npass\ndef __download_once(self, resource_identifier: str, output_path: str):\n\"\"\"Downloads a direct-download-link file by streaming its contents. source:\n        https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests\n        \"\"\"\nwith requests.get(resource_identifier, stream=True) as res:\nif res.status_code != 200:\nlog_response_error(res)\nmsg = (\n\"There was a problem retrieving the specified file at \"\n+ resource_identifier\n)\nraise CommunicationRetrievalError(msg)\nwith open(output_path, \"wb\") as f:\nfor chunk in res.iter_content(chunk_size=config.ddl_stream_chunk_size):\n# NOTE: if the response is chunk-encoded, this may not work\n# check whether this is common.\nf.write(chunk)\ndef download(self, resource_identifier: str, output_path: str):\n\"\"\"Downloads a direct-download-link file with multiple attempts. This is\n        done due to facing transient network failure from some direct download\n        link servers.\"\"\"\nattempt = 0\nwhile attempt < config.ddl_max_redownload_attempts:\ntry:\nself.__download_once(resource_identifier, output_path)\nreturn\nexcept CommunicationRetrievalError:\nif os.path.exists(output_path):\nremove_path(output_path)\nattempt += 1\nraise CommunicationRetrievalError(f\"Could not download {resource_identifier}\")\n
    "},{"location":"reference/comms/entity_resources/sources/direct/#comms.entity_resources.sources.direct.DirectLinkSource.__download_once","title":"__download_once(resource_identifier, output_path)","text":"

    Downloads a direct-download-link file by streaming its contents. source: https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests

    Source code in cli/medperf/comms/entity_resources/sources/direct.py
    def __download_once(self, resource_identifier: str, output_path: str):\n\"\"\"Downloads a direct-download-link file by streaming its contents. source:\n    https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests\n    \"\"\"\nwith requests.get(resource_identifier, stream=True) as res:\nif res.status_code != 200:\nlog_response_error(res)\nmsg = (\n\"There was a problem retrieving the specified file at \"\n+ resource_identifier\n)\nraise CommunicationRetrievalError(msg)\nwith open(output_path, \"wb\") as f:\nfor chunk in res.iter_content(chunk_size=config.ddl_stream_chunk_size):\n# NOTE: if the response is chunk-encoded, this may not work\n# check whether this is common.\nf.write(chunk)\n
    "},{"location":"reference/comms/entity_resources/sources/direct/#comms.entity_resources.sources.direct.DirectLinkSource.download","title":"download(resource_identifier, output_path)","text":"

    Downloads a direct-download-link file with multiple attempts. This is done due to facing transient network failure from some direct download link servers.

    Source code in cli/medperf/comms/entity_resources/sources/direct.py
    def download(self, resource_identifier: str, output_path: str):\n\"\"\"Downloads a direct-download-link file with multiple attempts. This is\n    done due to facing transient network failure from some direct download\n    link servers.\"\"\"\nattempt = 0\nwhile attempt < config.ddl_max_redownload_attempts:\ntry:\nself.__download_once(resource_identifier, output_path)\nreturn\nexcept CommunicationRetrievalError:\nif os.path.exists(output_path):\nremove_path(output_path)\nattempt += 1\nraise CommunicationRetrievalError(f\"Could not download {resource_identifier}\")\n
    "},{"location":"reference/comms/entity_resources/sources/direct/#comms.entity_resources.sources.direct.DirectLinkSource.validate_resource","title":"validate_resource(value) classmethod","text":"

    This class expects a resource string of the form direct:<URL> or only a URL.

    Parameters:

    Name Type Description Default resource str

    the resource string

    required

    Returns:

    Type Description str | None

    The URL if the pattern matches, else None

    Source code in cli/medperf/comms/entity_resources/sources/direct.py
    @classmethod\ndef validate_resource(cls, value: str):\n\"\"\"This class expects a resource string of the form\n    `direct:<URL>` or only a URL.\n    Args:\n        resource (str): the resource string\n    Returns:\n        (str|None): The URL if the pattern matches, else None\n    \"\"\"\nprefix = cls.prefix\nif value.startswith(prefix):\nprefix_len = len(prefix)\nvalue = value[prefix_len:]\nif validators.url(value):\nreturn value\n
    "},{"location":"reference/comms/entity_resources/sources/source/","title":"Source","text":""},{"location":"reference/comms/entity_resources/sources/source/#comms.entity_resources.sources.source.BaseSource","title":"BaseSource","text":"

    Bases: ABC

    Source code in cli/medperf/comms/entity_resources/sources/source.py
    class BaseSource(ABC):\n@classmethod\n@abstractmethod\ndef validate_resource(cls, value: str):\n\"\"\"Checks if an input resource can be downloaded by this class\"\"\"\n@abstractmethod\ndef __init__(self):\n\"\"\"Initialize\"\"\"\n@abstractmethod\ndef authenticate(self):\n\"\"\"Authenticates with the source server, if needed.\"\"\"\n@abstractmethod\ndef download(self, resource_identifier: str, output_path: str):\n\"\"\"Downloads the requested resource to the specified location\n        Args:\n            resource_identifier (str): The identifier that is used to download\n            the resource (e.g. URL, asset ID, ...) It is the parsed output\n            by `validate_resource`\n            output_path (str): The path to download the resource to\n        \"\"\"\n
    "},{"location":"reference/comms/entity_resources/sources/source/#comms.entity_resources.sources.source.BaseSource.__init__","title":"__init__() abstractmethod","text":"

    Initialize

    Source code in cli/medperf/comms/entity_resources/sources/source.py
    @abstractmethod\ndef __init__(self):\n\"\"\"Initialize\"\"\"\n
    "},{"location":"reference/comms/entity_resources/sources/source/#comms.entity_resources.sources.source.BaseSource.authenticate","title":"authenticate() abstractmethod","text":"

    Authenticates with the source server, if needed.

    Source code in cli/medperf/comms/entity_resources/sources/source.py
    @abstractmethod\ndef authenticate(self):\n\"\"\"Authenticates with the source server, if needed.\"\"\"\n
    "},{"location":"reference/comms/entity_resources/sources/source/#comms.entity_resources.sources.source.BaseSource.download","title":"download(resource_identifier, output_path) abstractmethod","text":"

    Downloads the requested resource to the specified location

    Parameters:

    Name Type Description Default resource_identifier str

    The identifier that is used to download

    required output_path str

    The path to download the resource to

    required Source code in cli/medperf/comms/entity_resources/sources/source.py
    @abstractmethod\ndef download(self, resource_identifier: str, output_path: str):\n\"\"\"Downloads the requested resource to the specified location\n    Args:\n        resource_identifier (str): The identifier that is used to download\n        the resource (e.g. URL, asset ID, ...) It is the parsed output\n        by `validate_resource`\n        output_path (str): The path to download the resource to\n    \"\"\"\n
    "},{"location":"reference/comms/entity_resources/sources/source/#comms.entity_resources.sources.source.BaseSource.validate_resource","title":"validate_resource(value) abstractmethod classmethod","text":"

    Checks if an input resource can be downloaded by this class

    Source code in cli/medperf/comms/entity_resources/sources/source.py
    @classmethod\n@abstractmethod\ndef validate_resource(cls, value: str):\n\"\"\"Checks if an input resource can be downloaded by this class\"\"\"\n
    "},{"location":"reference/comms/entity_resources/sources/synapse/","title":"Synapse","text":""},{"location":"reference/comms/entity_resources/sources/synapse/#comms.entity_resources.sources.synapse.SynapseSource","title":"SynapseSource","text":"

    Bases: BaseSource

    Source code in cli/medperf/comms/entity_resources/sources/synapse.py
    class SynapseSource(BaseSource):\nprefix = \"synapse:\"\n@classmethod\ndef validate_resource(cls, value: str):\n\"\"\"This class expects a resource string of the form\n        `synapse:<synapse_id>`, where <synapse_id> is in the form `syn<Integer>`.\n        Args:\n            resource (str): the resource string\n        Returns:\n            (str|None): The synapse ID if the pattern matches, else None\n        \"\"\"\nprefix = cls.prefix\nif not value.startswith(prefix):\nreturn\nprefix_len = len(prefix)\nvalue = value[prefix_len:]\nif re.match(r\"syn\\d+$\", value):\nreturn value\ndef __init__(self):\nself.client = synapseclient.Synapse()\ndef authenticate(self):\ntry:\nself.client.login(silent=True)\nexcept SynapseNoCredentialsError:\nmsg = \"There was an attempt to download resources from the Synapse \"\nmsg += \"platform, but couldn't find Synapse credentials.\"\nmsg += \"\\nDid you run 'medperf auth synapse_login' before?\"\nraise CommunicationAuthenticationError(msg)\ndef download(self, resource_identifier: str, output_path: str):\n# we can specify target folder only. File name depends on how it was stored\ndownload_location = os.path.dirname(output_path)\nos.makedirs(download_location, exist_ok=True)\ntry:\nresource_file = self.client.get(\nresource_identifier, downloadLocation=download_location\n)\nexcept (SynapseHTTPError, SynapseUnmetAccessRestrictions) as e:\nraise CommunicationRetrievalError(str(e))\nresource_path = os.path.join(download_location, resource_file.name)\n# synapseclient may only throw a warning in some cases\n# (e.g. read permissions but no download permissions)\nif not os.path.exists(resource_path):\nraise CommunicationRetrievalError(\n\"There was a problem retrieving a file from Synapse\"\n)\nshutil.move(resource_path, output_path)\n
    "},{"location":"reference/comms/entity_resources/sources/synapse/#comms.entity_resources.sources.synapse.SynapseSource.validate_resource","title":"validate_resource(value) classmethod","text":"

    This class expects a resource string of the form synapse:<synapse_id>, where is in the form syn<Integer>.

    Parameters:

    Name Type Description Default resource str

    the resource string

    required

    Returns:

    Type Description str | None

    The synapse ID if the pattern matches, else None

    Source code in cli/medperf/comms/entity_resources/sources/synapse.py
    @classmethod\ndef validate_resource(cls, value: str):\n\"\"\"This class expects a resource string of the form\n    `synapse:<synapse_id>`, where <synapse_id> is in the form `syn<Integer>`.\n    Args:\n        resource (str): the resource string\n    Returns:\n        (str|None): The synapse ID if the pattern matches, else None\n    \"\"\"\nprefix = cls.prefix\nif not value.startswith(prefix):\nreturn\nprefix_len = len(prefix)\nvalue = value[prefix_len:]\nif re.match(r\"syn\\d+$\", value):\nreturn value\n
    "},{"location":"reference/config_management/config_management/","title":"Config management","text":""},{"location":"reference/entities/benchmark/","title":"Benchmark","text":""},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark","title":"Benchmark","text":"

    Bases: Entity, Uploadable, MedperfSchema, ApprovableSchema, DeployableSchema

    Class representing a Benchmark

    a benchmark is a bundle of assets that enables quantitative measurement of the performance of AI models for a specific clinical problem. A Benchmark instance contains information regarding how to prepare datasets for execution, as well as what models to run and how to evaluate them.

    Source code in cli/medperf/entities/benchmark.py
    class Benchmark(Entity, Uploadable, MedperfSchema, ApprovableSchema, DeployableSchema):\n\"\"\"\n    Class representing a Benchmark\n    a benchmark is a bundle of assets that enables quantitative\n    measurement of the performance of AI models for a specific\n    clinical problem. A Benchmark instance contains information\n    regarding how to prepare datasets for execution, as well as\n    what models to run and how to evaluate them.\n    \"\"\"\ndescription: Optional[str] = Field(None, max_length=20)\ndocs_url: Optional[HttpUrl]\ndemo_dataset_tarball_url: str\ndemo_dataset_tarball_hash: Optional[str]\ndemo_dataset_generated_uid: Optional[str]\ndata_preparation_mlcube: int\nreference_model_mlcube: int\ndata_evaluator_mlcube: int\nmetadata: dict = {}\nuser_metadata: dict = {}\nis_active: bool = True\ndef __init__(self, *args, **kwargs):\n\"\"\"Creates a new benchmark instance\n        Args:\n            bmk_desc (Union[dict, BenchmarkModel]): Benchmark instance description\n        \"\"\"\nsuper().__init__(*args, **kwargs)\nself.generated_uid = f\"p{self.data_preparation_mlcube}m{self.reference_model_mlcube}e{self.data_evaluator_mlcube}\"\npath = config.benchmarks_folder\nif self.id:\npath = os.path.join(path, str(self.id))\nelse:\npath = os.path.join(path, self.generated_uid)\nself.path = path\n@classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Benchmark\"]:\n\"\"\"Gets and creates instances of all retrievable benchmarks\n        Args:\n            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n        Returns:\n            List[Benchmark]: a list of Benchmark instances.\n        \"\"\"\nlogging.info(\"Retrieving all benchmarks\")\nbenchmarks = []\nif not local_only:\nbenchmarks = cls.__remote_all(filters=filters)\nremote_uids = set([bmk.id for bmk in benchmarks])\nlocal_benchmarks = cls.__local_all()\nbenchmarks += [bmk for bmk in local_benchmarks if bmk.id not in remote_uids]\nreturn benchmarks\n@classmethod\ndef __remote_all(cls, filters: dict) -> List[\"Benchmark\"]:\nbenchmarks = []\ntry:\ncomms_fn = cls.__remote_prefilter(filters)\nbmks_meta = comms_fn()\nbenchmarks = [cls(**meta) for meta in bmks_meta]\nexcept CommunicationRetrievalError:\nmsg = \"Couldn't retrieve all benchmarks from the server\"\nlogging.warning(msg)\nreturn benchmarks\n@classmethod\ndef __remote_prefilter(cls, filters: dict) -> callable:\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n        Args:\n            filters (dict): filters to apply\n        Returns:\n            callable: A function for retrieving remote entities with the applied prefilters\n        \"\"\"\ncomms_fn = config.comms.get_benchmarks\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_benchmarks\nreturn comms_fn\n@classmethod\ndef __local_all(cls) -> List[\"Benchmark\"]:\nbenchmarks = []\nbmks_storage = config.benchmarks_folder\ntry:\nuids = next(os.walk(bmks_storage))[1]\nexcept StopIteration:\nmsg = \"Couldn't iterate over benchmarks directory\"\nlogging.warning(msg)\nraise MedperfException(msg)\nfor uid in uids:\nmeta = cls.__get_local_dict(uid)\nbenchmark = cls(**meta)\nbenchmarks.append(benchmark)\nreturn benchmarks\n@classmethod\ndef get(\ncls, benchmark_uid: Union[str, int], local_only: bool = False\n) -> \"Benchmark\":\n\"\"\"Retrieves and creates a Benchmark instance from the server.\n        If benchmark already exists in the platform then retrieve that\n        version.\n        Args:\n            benchmark_uid (str): UID of the benchmark.\n            comms (Comms): Instance of a communication interface.\n        Returns:\n            Benchmark: a Benchmark instance with the retrieved data.\n        \"\"\"\nif not str(benchmark_uid).isdigit() or local_only:\nreturn cls.__local_get(benchmark_uid)\ntry:\nreturn cls.__remote_get(benchmark_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting Benchmark {benchmark_uid} from comms failed\")\nlogging.info(f\"Looking for benchmark {benchmark_uid} locally\")\nreturn cls.__local_get(benchmark_uid)\n@classmethod\ndef __remote_get(cls, benchmark_uid: int) -> \"Benchmark\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n        If the dataset is present in the user's machine then it retrieves it from there.\n        Args:\n            dset_uid (str): server UID of the dataset\n        Returns:\n            Dataset: Specified Dataset Instance\n        \"\"\"\nlogging.debug(f\"Retrieving benchmark {benchmark_uid} remotely\")\nbenchmark_dict = config.comms.get_benchmark(benchmark_uid)\nbenchmark = cls(**benchmark_dict)\nbenchmark.write()\nreturn benchmark\n@classmethod\ndef __local_get(cls, benchmark_uid: Union[str, int]) -> \"Benchmark\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n        If the dataset is present in the user's machine then it retrieves it from there.\n        Args:\n            dset_uid (str): server UID of the dataset\n        Returns:\n            Dataset: Specified Dataset Instance\n        \"\"\"\nlogging.debug(f\"Retrieving benchmark {benchmark_uid} locally\")\nbenchmark_dict = cls.__get_local_dict(benchmark_uid)\nbenchmark = cls(**benchmark_dict)\nreturn benchmark\n@classmethod\ndef __get_local_dict(cls, benchmark_uid) -> dict:\n\"\"\"Retrieves a local benchmark information\n        Args:\n            benchmark_uid (str): uid of the local benchmark\n        Returns:\n            dict: information of the benchmark\n        \"\"\"\nlogging.info(f\"Retrieving benchmark {benchmark_uid} from local storage\")\nstorage = config.benchmarks_folder\nbmk_storage = os.path.join(storage, str(benchmark_uid))\nbmk_file = os.path.join(bmk_storage, config.benchmarks_filename)\nif not os.path.exists(bmk_file):\nraise InvalidArgumentError(\"No benchmark with the given uid could be found\")\nwith open(bmk_file, \"r\") as f:\ndata = yaml.safe_load(f)\nreturn data\n@classmethod\ndef get_models_uids(cls, benchmark_uid: int) -> List[int]:\n\"\"\"Retrieves the list of models associated to the benchmark\n        Args:\n            benchmark_uid (int): UID of the benchmark.\n            comms (Comms): Instance of the communications interface.\n        Returns:\n            List[int]: List of mlcube uids\n        \"\"\"\nassociations = config.comms.get_benchmark_model_associations(benchmark_uid)\nmodels_uids = [\nassoc[\"model_mlcube\"]\nfor assoc in associations\nif assoc[\"approval_status\"] == \"APPROVED\"\n]\nreturn models_uids\ndef todict(self) -> dict:\n\"\"\"Dictionary representation of the benchmark instance\n        Returns:\n        dict: Dictionary containing benchmark information\n        \"\"\"\nreturn self.extended_dict()\ndef write(self) -> str:\n\"\"\"Writes the benchmark into disk\n        Args:\n            filename (str, optional): name of the file. Defaults to config.benchmarks_filename.\n        Returns:\n            str: path to the created benchmark file\n        \"\"\"\ndata = self.todict()\nbmk_file = os.path.join(self.path, config.benchmarks_filename)\nif not os.path.exists(bmk_file):\nos.makedirs(self.path, exist_ok=True)\nwith open(bmk_file, \"w\") as f:\nyaml.dump(data, f)\nreturn bmk_file\ndef upload(self):\n\"\"\"Uploads a benchmark to the server\n        Args:\n            comms (Comms): communications entity to submit through\n        \"\"\"\nif self.for_test:\nraise InvalidArgumentError(\"Cannot upload test benchmarks.\")\nbody = self.todict()\nupdated_body = config.comms.upload_benchmark(body)\nreturn updated_body\ndef display_dict(self):\nreturn {\n\"UID\": self.identifier,\n\"Name\": self.name,\n\"Description\": self.description,\n\"Documentation\": self.docs_url,\n\"Created At\": self.created_at,\n\"Data Preparation MLCube\": int(self.data_preparation_mlcube),\n\"Reference Model MLCube\": int(self.reference_model_mlcube),\n\"Data Evaluator MLCube\": int(self.data_evaluator_mlcube),\n\"State\": self.state,\n\"Approval Status\": self.approval_status,\n\"Registered\": self.is_registered,\n}\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.__get_local_dict","title":"__get_local_dict(benchmark_uid) classmethod","text":"

    Retrieves a local benchmark information

    Parameters:

    Name Type Description Default benchmark_uid str

    uid of the local benchmark

    required

    Returns:

    Name Type Description dict dict

    information of the benchmark

    Source code in cli/medperf/entities/benchmark.py
    @classmethod\ndef __get_local_dict(cls, benchmark_uid) -> dict:\n\"\"\"Retrieves a local benchmark information\n    Args:\n        benchmark_uid (str): uid of the local benchmark\n    Returns:\n        dict: information of the benchmark\n    \"\"\"\nlogging.info(f\"Retrieving benchmark {benchmark_uid} from local storage\")\nstorage = config.benchmarks_folder\nbmk_storage = os.path.join(storage, str(benchmark_uid))\nbmk_file = os.path.join(bmk_storage, config.benchmarks_filename)\nif not os.path.exists(bmk_file):\nraise InvalidArgumentError(\"No benchmark with the given uid could be found\")\nwith open(bmk_file, \"r\") as f:\ndata = yaml.safe_load(f)\nreturn data\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.__init__","title":"__init__(*args, **kwargs)","text":"

    Creates a new benchmark instance

    Parameters:

    Name Type Description Default bmk_desc Union[dict, BenchmarkModel]

    Benchmark instance description

    required Source code in cli/medperf/entities/benchmark.py
    def __init__(self, *args, **kwargs):\n\"\"\"Creates a new benchmark instance\n    Args:\n        bmk_desc (Union[dict, BenchmarkModel]): Benchmark instance description\n    \"\"\"\nsuper().__init__(*args, **kwargs)\nself.generated_uid = f\"p{self.data_preparation_mlcube}m{self.reference_model_mlcube}e{self.data_evaluator_mlcube}\"\npath = config.benchmarks_folder\nif self.id:\npath = os.path.join(path, str(self.id))\nelse:\npath = os.path.join(path, self.generated_uid)\nself.path = path\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.__local_get","title":"__local_get(benchmark_uid) classmethod","text":"

    Retrieves and creates a Dataset instance from the comms instance. If the dataset is present in the user's machine then it retrieves it from there.

    Parameters:

    Name Type Description Default dset_uid str

    server UID of the dataset

    required

    Returns:

    Name Type Description Dataset Benchmark

    Specified Dataset Instance

    Source code in cli/medperf/entities/benchmark.py
    @classmethod\ndef __local_get(cls, benchmark_uid: Union[str, int]) -> \"Benchmark\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n    If the dataset is present in the user's machine then it retrieves it from there.\n    Args:\n        dset_uid (str): server UID of the dataset\n    Returns:\n        Dataset: Specified Dataset Instance\n    \"\"\"\nlogging.debug(f\"Retrieving benchmark {benchmark_uid} locally\")\nbenchmark_dict = cls.__get_local_dict(benchmark_uid)\nbenchmark = cls(**benchmark_dict)\nreturn benchmark\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.__remote_get","title":"__remote_get(benchmark_uid) classmethod","text":"

    Retrieves and creates a Dataset instance from the comms instance. If the dataset is present in the user's machine then it retrieves it from there.

    Parameters:

    Name Type Description Default dset_uid str

    server UID of the dataset

    required

    Returns:

    Name Type Description Dataset Benchmark

    Specified Dataset Instance

    Source code in cli/medperf/entities/benchmark.py
    @classmethod\ndef __remote_get(cls, benchmark_uid: int) -> \"Benchmark\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n    If the dataset is present in the user's machine then it retrieves it from there.\n    Args:\n        dset_uid (str): server UID of the dataset\n    Returns:\n        Dataset: Specified Dataset Instance\n    \"\"\"\nlogging.debug(f\"Retrieving benchmark {benchmark_uid} remotely\")\nbenchmark_dict = config.comms.get_benchmark(benchmark_uid)\nbenchmark = cls(**benchmark_dict)\nbenchmark.write()\nreturn benchmark\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.__remote_prefilter","title":"__remote_prefilter(filters) classmethod","text":"

    Applies filtering logic that must be done before retrieving remote entities

    Parameters:

    Name Type Description Default filters dict

    filters to apply

    required

    Returns:

    Name Type Description callable callable

    A function for retrieving remote entities with the applied prefilters

    Source code in cli/medperf/entities/benchmark.py
    @classmethod\ndef __remote_prefilter(cls, filters: dict) -> callable:\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n    Args:\n        filters (dict): filters to apply\n    Returns:\n        callable: A function for retrieving remote entities with the applied prefilters\n    \"\"\"\ncomms_fn = config.comms.get_benchmarks\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_benchmarks\nreturn comms_fn\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.all","title":"all(local_only=False, filters={}) classmethod","text":"

    Gets and creates instances of all retrievable benchmarks

    Parameters:

    Name Type Description Default local_only bool

    Wether to retrieve only local entities. Defaults to False.

    False filters dict

    key-value pairs specifying filters to apply to the list of entities.

    {}

    Returns:

    Type Description List[Benchmark]

    List[Benchmark]: a list of Benchmark instances.

    Source code in cli/medperf/entities/benchmark.py
    @classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Benchmark\"]:\n\"\"\"Gets and creates instances of all retrievable benchmarks\n    Args:\n        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n    Returns:\n        List[Benchmark]: a list of Benchmark instances.\n    \"\"\"\nlogging.info(\"Retrieving all benchmarks\")\nbenchmarks = []\nif not local_only:\nbenchmarks = cls.__remote_all(filters=filters)\nremote_uids = set([bmk.id for bmk in benchmarks])\nlocal_benchmarks = cls.__local_all()\nbenchmarks += [bmk for bmk in local_benchmarks if bmk.id not in remote_uids]\nreturn benchmarks\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.get","title":"get(benchmark_uid, local_only=False) classmethod","text":"

    Retrieves and creates a Benchmark instance from the server. If benchmark already exists in the platform then retrieve that version.

    Parameters:

    Name Type Description Default benchmark_uid str

    UID of the benchmark.

    required comms Comms

    Instance of a communication interface.

    required

    Returns:

    Name Type Description Benchmark Benchmark

    a Benchmark instance with the retrieved data.

    Source code in cli/medperf/entities/benchmark.py
    @classmethod\ndef get(\ncls, benchmark_uid: Union[str, int], local_only: bool = False\n) -> \"Benchmark\":\n\"\"\"Retrieves and creates a Benchmark instance from the server.\n    If benchmark already exists in the platform then retrieve that\n    version.\n    Args:\n        benchmark_uid (str): UID of the benchmark.\n        comms (Comms): Instance of a communication interface.\n    Returns:\n        Benchmark: a Benchmark instance with the retrieved data.\n    \"\"\"\nif not str(benchmark_uid).isdigit() or local_only:\nreturn cls.__local_get(benchmark_uid)\ntry:\nreturn cls.__remote_get(benchmark_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting Benchmark {benchmark_uid} from comms failed\")\nlogging.info(f\"Looking for benchmark {benchmark_uid} locally\")\nreturn cls.__local_get(benchmark_uid)\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.get_models_uids","title":"get_models_uids(benchmark_uid) classmethod","text":"

    Retrieves the list of models associated to the benchmark

    Parameters:

    Name Type Description Default benchmark_uid int

    UID of the benchmark.

    required comms Comms

    Instance of the communications interface.

    required

    Returns:

    Type Description List[int]

    List[int]: List of mlcube uids

    Source code in cli/medperf/entities/benchmark.py
    @classmethod\ndef get_models_uids(cls, benchmark_uid: int) -> List[int]:\n\"\"\"Retrieves the list of models associated to the benchmark\n    Args:\n        benchmark_uid (int): UID of the benchmark.\n        comms (Comms): Instance of the communications interface.\n    Returns:\n        List[int]: List of mlcube uids\n    \"\"\"\nassociations = config.comms.get_benchmark_model_associations(benchmark_uid)\nmodels_uids = [\nassoc[\"model_mlcube\"]\nfor assoc in associations\nif assoc[\"approval_status\"] == \"APPROVED\"\n]\nreturn models_uids\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.todict","title":"todict()","text":"

    Dictionary representation of the benchmark instance

    dict: Dictionary containing benchmark information

    Source code in cli/medperf/entities/benchmark.py
    def todict(self) -> dict:\n\"\"\"Dictionary representation of the benchmark instance\n    Returns:\n    dict: Dictionary containing benchmark information\n    \"\"\"\nreturn self.extended_dict()\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.upload","title":"upload()","text":"

    Uploads a benchmark to the server

    Parameters:

    Name Type Description Default comms Comms

    communications entity to submit through

    required Source code in cli/medperf/entities/benchmark.py
    def upload(self):\n\"\"\"Uploads a benchmark to the server\n    Args:\n        comms (Comms): communications entity to submit through\n    \"\"\"\nif self.for_test:\nraise InvalidArgumentError(\"Cannot upload test benchmarks.\")\nbody = self.todict()\nupdated_body = config.comms.upload_benchmark(body)\nreturn updated_body\n
    "},{"location":"reference/entities/benchmark/#entities.benchmark.Benchmark.write","title":"write()","text":"

    Writes the benchmark into disk

    Parameters:

    Name Type Description Default filename str

    name of the file. Defaults to config.benchmarks_filename.

    required

    Returns:

    Name Type Description str str

    path to the created benchmark file

    Source code in cli/medperf/entities/benchmark.py
    def write(self) -> str:\n\"\"\"Writes the benchmark into disk\n    Args:\n        filename (str, optional): name of the file. Defaults to config.benchmarks_filename.\n    Returns:\n        str: path to the created benchmark file\n    \"\"\"\ndata = self.todict()\nbmk_file = os.path.join(self.path, config.benchmarks_filename)\nif not os.path.exists(bmk_file):\nos.makedirs(self.path, exist_ok=True)\nwith open(bmk_file, \"w\") as f:\nyaml.dump(data, f)\nreturn bmk_file\n
    "},{"location":"reference/entities/cube/","title":"Cube","text":""},{"location":"reference/entities/cube/#entities.cube.Cube","title":"Cube","text":"

    Bases: Entity, Uploadable, MedperfSchema, DeployableSchema

    Class representing an MLCube Container

    Medperf platform uses the MLCube container for components such as Dataset Preparation, Evaluation, and the Registered Models. MLCube containers are software containers (e.g., Docker and Singularity) with standard metadata and a consistent file-system level interface.

    Source code in cli/medperf/entities/cube.py
    class Cube(Entity, Uploadable, MedperfSchema, DeployableSchema):\n\"\"\"\n    Class representing an MLCube Container\n    Medperf platform uses the MLCube container for components such as\n    Dataset Preparation, Evaluation, and the Registered Models. MLCube\n    containers are software containers (e.g., Docker and Singularity)\n    with standard metadata and a consistent file-system level interface.\n    \"\"\"\ngit_mlcube_url: str\nmlcube_hash: Optional[str]\ngit_parameters_url: Optional[str]\nparameters_hash: Optional[str]\nimage_tarball_url: Optional[str]\nimage_tarball_hash: Optional[str]\nimage_hash: Optional[str]\nadditional_files_tarball_url: Optional[str] = Field(None, alias=\"tarball_url\")\nadditional_files_tarball_hash: Optional[str] = Field(None, alias=\"tarball_hash\")\nmetadata: dict = {}\nuser_metadata: dict = {}\ndef __init__(self, *args, **kwargs):\n\"\"\"Creates a Cube instance\n        Args:\n            cube_desc (Union[dict, CubeModel]): MLCube Instance description\n        \"\"\"\nsuper().__init__(*args, **kwargs)\nself.generated_uid = self.name\npath = config.cubes_folder\nif self.id:\npath = os.path.join(path, str(self.id))\nelse:\npath = os.path.join(path, self.generated_uid)\n# NOTE: maybe have these as @property, to have the same entity reusable\n#       before and after submission\nself.path = path\nself.cube_path = os.path.join(path, config.cube_filename)\nself.params_path = None\nif self.git_parameters_url:\nself.params_path = os.path.join(path, config.params_filename)\n@classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Cube\"]:\n\"\"\"Class method for retrieving all retrievable MLCubes\n        Args:\n            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n        Returns:\n            List[Cube]: List containing all cubes\n        \"\"\"\nlogging.info(\"Retrieving all cubes\")\ncubes = []\nif not local_only:\ncubes = cls.__remote_all(filters=filters)\nremote_uids = set([cube.id for cube in cubes])\nlocal_cubes = cls.__local_all()\ncubes += [cube for cube in local_cubes if cube.id not in remote_uids]\nreturn cubes\n@classmethod\ndef __remote_all(cls, filters: dict) -> List[\"Cube\"]:\ncubes = []\ntry:\ncomms_fn = cls.__remote_prefilter(filters)\ncubes_meta = comms_fn()\ncubes = [cls(**meta) for meta in cubes_meta]\nexcept CommunicationRetrievalError:\nmsg = \"Couldn't retrieve all cubes from the server\"\nlogging.warning(msg)\nreturn cubes\n@classmethod\ndef __remote_prefilter(cls, filters: dict):\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n        Args:\n            filters (dict): filters to apply\n        Returns:\n            callable: A function for retrieving remote entities with the applied prefilters\n        \"\"\"\ncomms_fn = config.comms.get_cubes\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_cubes\nreturn comms_fn\n@classmethod\ndef __local_all(cls) -> List[\"Cube\"]:\ncubes = []\ncubes_folder = config.cubes_folder\ntry:\nuids = next(os.walk(cubes_folder))[1]\nlogging.debug(f\"Local cubes found: {uids}\")\nexcept StopIteration:\nmsg = \"Couldn't iterate over cubes directory\"\nlogging.warning(msg)\nraise MedperfException(msg)\nfor uid in uids:\nmeta = cls.__get_local_dict(uid)\ncube = cls(**meta)\ncubes.append(cube)\nreturn cubes\n@classmethod\ndef get(cls, cube_uid: Union[str, int], local_only: bool = False) -> \"Cube\":\n\"\"\"Retrieves and creates a Cube instance from the comms. If cube already exists\n        inside the user's computer then retrieves it from there.\n        Args:\n            cube_uid (str): UID of the cube.\n        Returns:\n            Cube : a Cube instance with the retrieved data.\n        \"\"\"\nif not str(cube_uid).isdigit() or local_only:\ncube = cls.__local_get(cube_uid)\nelse:\ntry:\ncube = cls.__remote_get(cube_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting MLCube {cube_uid} from comms failed\")\nlogging.info(f\"Retrieving MLCube {cube_uid} from local storage\")\ncube = cls.__local_get(cube_uid)\nif not cube.is_valid:\nraise InvalidEntityError(\"The requested MLCube is marked as INVALID.\")\ncube.download_config_files()\nreturn cube\n@classmethod\ndef __remote_get(cls, cube_uid: int) -> \"Cube\":\nlogging.debug(f\"Retrieving mlcube {cube_uid} remotely\")\nmeta = config.comms.get_cube_metadata(cube_uid)\ncube = cls(**meta)\ncube.write()\nreturn cube\n@classmethod\ndef __local_get(cls, cube_uid: Union[str, int]) -> \"Cube\":\nlogging.debug(f\"Retrieving cube {cube_uid} locally\")\nlocal_meta = cls.__get_local_dict(cube_uid)\ncube = cls(**local_meta)\nreturn cube\ndef download_mlcube(self):\nurl = self.git_mlcube_url\npath, file_hash = resources.get_cube(url, self.path, self.mlcube_hash)\nself.cube_path = path\nself.mlcube_hash = file_hash\ndef download_parameters(self):\nurl = self.git_parameters_url\nif url:\npath, file_hash = resources.get_cube_params(\nurl, self.path, self.parameters_hash\n)\nself.params_path = path\nself.parameters_hash = file_hash\ndef download_additional(self):\nurl = self.additional_files_tarball_url\nif url:\nfile_hash = resources.get_cube_additional(\nurl, self.path, self.additional_files_tarball_hash\n)\nself.additional_files_tarball_hash = file_hash\ndef download_image(self):\nurl = self.image_tarball_url\ntarball_hash = self.image_tarball_hash\nif url:\n_, local_hash = resources.get_cube_image(url, self.path, tarball_hash)\nself.image_tarball_hash = local_hash\nelse:\nif config.platform == \"docker\":\n# For docker, image should be pulled before calculating its hash\nself._get_image_from_registry()\nself._set_image_hash_from_registry()\nelif config.platform == \"singularity\":\n# For singularity, we need the hash first before trying to convert\nself._set_image_hash_from_registry()\nimage_folder = os.path.join(config.cubes_folder, config.image_path)\nif os.path.exists(image_folder):\nfor file in os.listdir(image_folder):\nif file == self._converted_singularity_image_name:\nreturn\nremove_path(os.path.join(image_folder, file))\nself._get_image_from_registry()\nelse:\n# TODO: such a check should happen on commands entrypoints, not here\nraise InvalidArgumentError(\"Unsupported platform\")\n@property\ndef _converted_singularity_image_name(self):\nreturn f\"{self.image_hash}.sif\"\ndef _set_image_hash_from_registry(self):\n# Retrieve image hash from MLCube\nlogging.debug(f\"Retrieving {self.id} image hash\")\ntmp_out_yaml = generate_tmp_path()\ncmd = f\"mlcube --log-level {config.loglevel} inspect --mlcube={self.cube_path} --format=yaml\"\ncmd += f\" --platform={config.platform} --output-file {tmp_out_yaml}\"\nlogging.info(f\"Running MLCube command: {cmd}\")\nwith spawn_and_kill(cmd, timeout=config.mlcube_inspect_timeout) as proc_wrapper:\nproc = proc_wrapper.proc\ncombine_proc_sp_text(proc)\nif proc.exitstatus != 0:\nraise ExecutionError(\"There was an error while inspecting the image hash\")\nwith open(tmp_out_yaml) as f:\nmlcube_details = yaml.safe_load(f)\nremove_path(tmp_out_yaml)\nlocal_hash = mlcube_details[\"hash\"]\nif self.image_hash and local_hash != self.image_hash:\nraise InvalidEntityError(\nf\"Hash mismatch. Expected {self.image_hash}, found {local_hash}.\"\n)\nself.image_hash = local_hash\ndef _get_image_from_registry(self):\n# Retrieve image from image registry\nlogging.debug(f\"Retrieving {self.id} image\")\ncmd = f\"mlcube --log-level {config.loglevel} configure --mlcube={self.cube_path} --platform={config.platform}\"\nif config.platform == \"singularity\":\ncmd += f\" -Psingularity.image={self._converted_singularity_image_name}\"\nlogging.info(f\"Running MLCube command: {cmd}\")\nwith spawn_and_kill(\ncmd, timeout=config.mlcube_configure_timeout\n) as proc_wrapper:\nproc = proc_wrapper.proc\ncombine_proc_sp_text(proc)\nif proc.exitstatus != 0:\nraise ExecutionError(\"There was an error while retrieving the MLCube image\")\ndef download_config_files(self):\ntry:\nself.download_mlcube()\nexcept InvalidEntityError as e:\nraise InvalidEntityError(f\"MLCube {self.name} manifest file: {e}\")\ntry:\nself.download_parameters()\nexcept InvalidEntityError as e:\nraise InvalidEntityError(f\"MLCube {self.name} parameters file: {e}\")\ndef download_run_files(self):\ntry:\nself.download_additional()\nexcept InvalidEntityError as e:\nraise InvalidEntityError(f\"MLCube {self.name} additional files: {e}\")\ntry:\nself.download_image()\nexcept InvalidEntityError as e:\nraise InvalidEntityError(f\"MLCube {self.name} image file: {e}\")\ndef run(\nself,\ntask: str,\noutput_logs: str = None,\nstring_params: Dict[str, str] = {},\ntimeout: int = None,\nread_protected_input: bool = True,\n**kwargs,\n):\n\"\"\"Executes a given task on the cube instance\n        Args:\n            task (str): task to run\n            string_params (Dict[str], optional): Extra parameters that can't be passed as normal function args.\n                                                 Defaults to {}.\n            timeout (int, optional): timeout for the task in seconds. Defaults to None.\n            read_protected_input (bool, optional): Wether to disable write permissions on input volumes. Defaults to True.\n            kwargs (dict): additional arguments that are passed directly to the mlcube command\n        \"\"\"\nkwargs.update(string_params)\ncmd = f\"mlcube --log-level {config.loglevel} run\"\ncmd += f\" --mlcube={self.cube_path} --task={task} --platform={config.platform} --network=none\"\nif config.gpus is not None:\ncmd += f\" --gpus={config.gpus}\"\nif read_protected_input:\ncmd += \" --mount=ro\"\nfor k, v in kwargs.items():\ncmd_arg = f'{k}=\"{v}\"'\ncmd = \" \".join([cmd, cmd_arg])\ncontainer_loglevel = config.container_loglevel\n# TODO: we should override run args instead of what we are doing below\n#       we shouldn't allow arbitrary run args unless our client allows it\nif config.platform == \"docker\":\n# use current user\ncpu_args = self.get_config(\"docker.cpu_args\") or \"\"\ngpu_args = self.get_config(\"docker.gpu_args\") or \"\"\ncpu_args = \" \".join([cpu_args, \"-u $(id -u):$(id -g)\"]).strip()\ngpu_args = \" \".join([gpu_args, \"-u $(id -u):$(id -g)\"]).strip()\ncmd += f' -Pdocker.cpu_args=\"{cpu_args}\"'\ncmd += f' -Pdocker.gpu_args=\"{gpu_args}\"'\nif container_loglevel:\ncmd += f' -Pdocker.env_args=\"-e MEDPERF_LOGLEVEL={container_loglevel.upper()}\"'\nelif config.platform == \"singularity\":\n# use -e to discard host env vars, -C to isolate the container (see singularity run --help)\nrun_args = self.get_config(\"singularity.run_args\") or \"\"\nrun_args = \" \".join([run_args, \"-eC\"]).strip()\ncmd += f' -Psingularity.run_args=\"{run_args}\"'\n# set image name in case of running docker image with singularity\n# Assuming we only accept mlcube.yamls with either singularity or docker sections\n# TODO: make checks on submitted mlcubes\nsingularity_config = self.get_config(\"singularity\")\nif singularity_config is None:\ncmd += (\nf' -Psingularity.image=\"{self._converted_singularity_image_name}\"'\n)\n# TODO: pass logging env for singularity also there\nelse:\nraise InvalidArgumentError(\"Unsupported platform\")\n# set accelerator count to zero to avoid unexpected behaviours and\n# force mlcube to only use --gpus to figure out GPU config\ncmd += \" -Pplatform.accelerator_count=0\"\nlogging.info(f\"Running MLCube command: {cmd}\")\nwith spawn_and_kill(cmd, timeout=timeout) as proc_wrapper:\nproc = proc_wrapper.proc\nproc_out = combine_proc_sp_text(proc)\nif output_logs is not None:\nwith open(output_logs, \"w\") as f:\nf.write(proc_out)\nif proc.exitstatus != 0:\nraise ExecutionError(\"There was an error while executing the cube\")\nlog_storage()\nreturn proc\ndef get_default_output(self, task: str, out_key: str, param_key: str = None) -> str:\n\"\"\"Returns the output parameter specified in the mlcube.yaml file\n        Args:\n            task (str): the task of interest\n            out_key (str): key used to identify the desired output in the yaml file\n            param_key (str): key inside the parameters file that completes the output path. Defaults to None.\n        Returns:\n            str: the path as specified in the mlcube.yaml file for the desired\n                output for the desired task. Defaults to None if out_key not found\n        \"\"\"\nout_path = self.get_config(f\"tasks.{task}.parameters.outputs.{out_key}\")\nif out_path is None:\nreturn\nif isinstance(out_path, dict):\n# output is specified as a dict with type and default values\nout_path = out_path[\"default\"]\ncube_loc = str(Path(self.cube_path).parent)\nout_path = os.path.join(cube_loc, \"workspace\", out_path)\nif self.params_path is not None and param_key is not None:\nwith open(self.params_path, \"r\") as f:\nparams = yaml.safe_load(f)\nout_path = os.path.join(out_path, params[param_key])\nreturn out_path\ndef get_config(self, identifier):\n\"\"\"\n        Returns the output parameter specified in the mlcube.yaml file\n        Args:\n            identifier (str): `.` separated keys to traverse the mlcube dict\n        Returns:\n            str: the parameter value, None if not found\n        \"\"\"\nwith open(self.cube_path, \"r\") as f:\ncube = yaml.safe_load(f)\nkeys = identifier.split(\".\")\nfor key in keys:\nif key not in cube:\nreturn\ncube = cube[key]\nreturn cube\ndef todict(self) -> Dict:\nreturn self.extended_dict()\ndef write(self):\ncube_loc = str(Path(self.cube_path).parent)\nmeta_file = os.path.join(cube_loc, config.cube_metadata_filename)\nos.makedirs(cube_loc, exist_ok=True)\nwith open(meta_file, \"w\") as f:\nyaml.dump(self.todict(), f)\nreturn meta_file\ndef upload(self):\nif self.for_test:\nraise InvalidArgumentError(\"Cannot upload test mlcubes.\")\ncube_dict = self.todict()\nupdated_cube_dict = config.comms.upload_mlcube(cube_dict)\nreturn updated_cube_dict\n@classmethod\ndef __get_local_dict(cls, uid):\ncubes_folder = config.cubes_folder\nmeta_file = os.path.join(cubes_folder, str(uid), config.cube_metadata_filename)\nif not os.path.exists(meta_file):\nraise InvalidArgumentError(\n\"The requested mlcube information could not be found locally\"\n)\nwith open(meta_file, \"r\") as f:\nmeta = yaml.safe_load(f)\nreturn meta\ndef display_dict(self):\nreturn {\n\"UID\": self.identifier,\n\"Name\": self.name,\n\"Config File\": self.git_mlcube_url,\n\"State\": self.state,\n\"Created At\": self.created_at,\n\"Registered\": self.is_registered,\n}\n
    "},{"location":"reference/entities/cube/#entities.cube.Cube.__init__","title":"__init__(*args, **kwargs)","text":"

    Creates a Cube instance

    Parameters:

    Name Type Description Default cube_desc Union[dict, CubeModel]

    MLCube Instance description

    required Source code in cli/medperf/entities/cube.py
    def __init__(self, *args, **kwargs):\n\"\"\"Creates a Cube instance\n    Args:\n        cube_desc (Union[dict, CubeModel]): MLCube Instance description\n    \"\"\"\nsuper().__init__(*args, **kwargs)\nself.generated_uid = self.name\npath = config.cubes_folder\nif self.id:\npath = os.path.join(path, str(self.id))\nelse:\npath = os.path.join(path, self.generated_uid)\n# NOTE: maybe have these as @property, to have the same entity reusable\n#       before and after submission\nself.path = path\nself.cube_path = os.path.join(path, config.cube_filename)\nself.params_path = None\nif self.git_parameters_url:\nself.params_path = os.path.join(path, config.params_filename)\n
    "},{"location":"reference/entities/cube/#entities.cube.Cube.__remote_prefilter","title":"__remote_prefilter(filters) classmethod","text":"

    Applies filtering logic that must be done before retrieving remote entities

    Parameters:

    Name Type Description Default filters dict

    filters to apply

    required

    Returns:

    Name Type Description callable

    A function for retrieving remote entities with the applied prefilters

    Source code in cli/medperf/entities/cube.py
    @classmethod\ndef __remote_prefilter(cls, filters: dict):\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n    Args:\n        filters (dict): filters to apply\n    Returns:\n        callable: A function for retrieving remote entities with the applied prefilters\n    \"\"\"\ncomms_fn = config.comms.get_cubes\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_cubes\nreturn comms_fn\n
    "},{"location":"reference/entities/cube/#entities.cube.Cube.all","title":"all(local_only=False, filters={}) classmethod","text":"

    Class method for retrieving all retrievable MLCubes

    Parameters:

    Name Type Description Default local_only bool

    Wether to retrieve only local entities. Defaults to False.

    False filters dict

    key-value pairs specifying filters to apply to the list of entities.

    {}

    Returns:

    Type Description List[Cube]

    List[Cube]: List containing all cubes

    Source code in cli/medperf/entities/cube.py
    @classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Cube\"]:\n\"\"\"Class method for retrieving all retrievable MLCubes\n    Args:\n        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n    Returns:\n        List[Cube]: List containing all cubes\n    \"\"\"\nlogging.info(\"Retrieving all cubes\")\ncubes = []\nif not local_only:\ncubes = cls.__remote_all(filters=filters)\nremote_uids = set([cube.id for cube in cubes])\nlocal_cubes = cls.__local_all()\ncubes += [cube for cube in local_cubes if cube.id not in remote_uids]\nreturn cubes\n
    "},{"location":"reference/entities/cube/#entities.cube.Cube.get","title":"get(cube_uid, local_only=False) classmethod","text":"

    Retrieves and creates a Cube instance from the comms. If cube already exists inside the user's computer then retrieves it from there.

    Parameters:

    Name Type Description Default cube_uid str

    UID of the cube.

    required

    Returns:

    Name Type Description Cube Cube

    a Cube instance with the retrieved data.

    Source code in cli/medperf/entities/cube.py
    @classmethod\ndef get(cls, cube_uid: Union[str, int], local_only: bool = False) -> \"Cube\":\n\"\"\"Retrieves and creates a Cube instance from the comms. If cube already exists\n    inside the user's computer then retrieves it from there.\n    Args:\n        cube_uid (str): UID of the cube.\n    Returns:\n        Cube : a Cube instance with the retrieved data.\n    \"\"\"\nif not str(cube_uid).isdigit() or local_only:\ncube = cls.__local_get(cube_uid)\nelse:\ntry:\ncube = cls.__remote_get(cube_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting MLCube {cube_uid} from comms failed\")\nlogging.info(f\"Retrieving MLCube {cube_uid} from local storage\")\ncube = cls.__local_get(cube_uid)\nif not cube.is_valid:\nraise InvalidEntityError(\"The requested MLCube is marked as INVALID.\")\ncube.download_config_files()\nreturn cube\n
    "},{"location":"reference/entities/cube/#entities.cube.Cube.get_config","title":"get_config(identifier)","text":"

    Returns the output parameter specified in the mlcube.yaml file

    Parameters:

    Name Type Description Default identifier str

    . separated keys to traverse the mlcube dict

    required

    Returns:

    Name Type Description str

    the parameter value, None if not found

    Source code in cli/medperf/entities/cube.py
    def get_config(self, identifier):\n\"\"\"\n    Returns the output parameter specified in the mlcube.yaml file\n    Args:\n        identifier (str): `.` separated keys to traverse the mlcube dict\n    Returns:\n        str: the parameter value, None if not found\n    \"\"\"\nwith open(self.cube_path, \"r\") as f:\ncube = yaml.safe_load(f)\nkeys = identifier.split(\".\")\nfor key in keys:\nif key not in cube:\nreturn\ncube = cube[key]\nreturn cube\n
    "},{"location":"reference/entities/cube/#entities.cube.Cube.get_default_output","title":"get_default_output(task, out_key, param_key=None)","text":"

    Returns the output parameter specified in the mlcube.yaml file

    Parameters:

    Name Type Description Default task str

    the task of interest

    required out_key str

    key used to identify the desired output in the yaml file

    required param_key str

    key inside the parameters file that completes the output path. Defaults to None.

    None

    Returns:

    Name Type Description str str

    the path as specified in the mlcube.yaml file for the desired output for the desired task. Defaults to None if out_key not found

    Source code in cli/medperf/entities/cube.py
    def get_default_output(self, task: str, out_key: str, param_key: str = None) -> str:\n\"\"\"Returns the output parameter specified in the mlcube.yaml file\n    Args:\n        task (str): the task of interest\n        out_key (str): key used to identify the desired output in the yaml file\n        param_key (str): key inside the parameters file that completes the output path. Defaults to None.\n    Returns:\n        str: the path as specified in the mlcube.yaml file for the desired\n            output for the desired task. Defaults to None if out_key not found\n    \"\"\"\nout_path = self.get_config(f\"tasks.{task}.parameters.outputs.{out_key}\")\nif out_path is None:\nreturn\nif isinstance(out_path, dict):\n# output is specified as a dict with type and default values\nout_path = out_path[\"default\"]\ncube_loc = str(Path(self.cube_path).parent)\nout_path = os.path.join(cube_loc, \"workspace\", out_path)\nif self.params_path is not None and param_key is not None:\nwith open(self.params_path, \"r\") as f:\nparams = yaml.safe_load(f)\nout_path = os.path.join(out_path, params[param_key])\nreturn out_path\n
    "},{"location":"reference/entities/cube/#entities.cube.Cube.run","title":"run(task, output_logs=None, string_params={}, timeout=None, read_protected_input=True, **kwargs)","text":"

    Executes a given task on the cube instance

    Parameters:

    Name Type Description Default task str

    task to run

    required string_params Dict[str]

    Extra parameters that can't be passed as normal function args. Defaults to {}.

    {} timeout int

    timeout for the task in seconds. Defaults to None.

    None read_protected_input bool

    Wether to disable write permissions on input volumes. Defaults to True.

    True kwargs dict

    additional arguments that are passed directly to the mlcube command

    {} Source code in cli/medperf/entities/cube.py
    def run(\nself,\ntask: str,\noutput_logs: str = None,\nstring_params: Dict[str, str] = {},\ntimeout: int = None,\nread_protected_input: bool = True,\n**kwargs,\n):\n\"\"\"Executes a given task on the cube instance\n    Args:\n        task (str): task to run\n        string_params (Dict[str], optional): Extra parameters that can't be passed as normal function args.\n                                             Defaults to {}.\n        timeout (int, optional): timeout for the task in seconds. Defaults to None.\n        read_protected_input (bool, optional): Wether to disable write permissions on input volumes. Defaults to True.\n        kwargs (dict): additional arguments that are passed directly to the mlcube command\n    \"\"\"\nkwargs.update(string_params)\ncmd = f\"mlcube --log-level {config.loglevel} run\"\ncmd += f\" --mlcube={self.cube_path} --task={task} --platform={config.platform} --network=none\"\nif config.gpus is not None:\ncmd += f\" --gpus={config.gpus}\"\nif read_protected_input:\ncmd += \" --mount=ro\"\nfor k, v in kwargs.items():\ncmd_arg = f'{k}=\"{v}\"'\ncmd = \" \".join([cmd, cmd_arg])\ncontainer_loglevel = config.container_loglevel\n# TODO: we should override run args instead of what we are doing below\n#       we shouldn't allow arbitrary run args unless our client allows it\nif config.platform == \"docker\":\n# use current user\ncpu_args = self.get_config(\"docker.cpu_args\") or \"\"\ngpu_args = self.get_config(\"docker.gpu_args\") or \"\"\ncpu_args = \" \".join([cpu_args, \"-u $(id -u):$(id -g)\"]).strip()\ngpu_args = \" \".join([gpu_args, \"-u $(id -u):$(id -g)\"]).strip()\ncmd += f' -Pdocker.cpu_args=\"{cpu_args}\"'\ncmd += f' -Pdocker.gpu_args=\"{gpu_args}\"'\nif container_loglevel:\ncmd += f' -Pdocker.env_args=\"-e MEDPERF_LOGLEVEL={container_loglevel.upper()}\"'\nelif config.platform == \"singularity\":\n# use -e to discard host env vars, -C to isolate the container (see singularity run --help)\nrun_args = self.get_config(\"singularity.run_args\") or \"\"\nrun_args = \" \".join([run_args, \"-eC\"]).strip()\ncmd += f' -Psingularity.run_args=\"{run_args}\"'\n# set image name in case of running docker image with singularity\n# Assuming we only accept mlcube.yamls with either singularity or docker sections\n# TODO: make checks on submitted mlcubes\nsingularity_config = self.get_config(\"singularity\")\nif singularity_config is None:\ncmd += (\nf' -Psingularity.image=\"{self._converted_singularity_image_name}\"'\n)\n# TODO: pass logging env for singularity also there\nelse:\nraise InvalidArgumentError(\"Unsupported platform\")\n# set accelerator count to zero to avoid unexpected behaviours and\n# force mlcube to only use --gpus to figure out GPU config\ncmd += \" -Pplatform.accelerator_count=0\"\nlogging.info(f\"Running MLCube command: {cmd}\")\nwith spawn_and_kill(cmd, timeout=timeout) as proc_wrapper:\nproc = proc_wrapper.proc\nproc_out = combine_proc_sp_text(proc)\nif output_logs is not None:\nwith open(output_logs, \"w\") as f:\nf.write(proc_out)\nif proc.exitstatus != 0:\nraise ExecutionError(\"There was an error while executing the cube\")\nlog_storage()\nreturn proc\n
    "},{"location":"reference/entities/dataset/","title":"Dataset","text":""},{"location":"reference/entities/dataset/#entities.dataset.Dataset","title":"Dataset","text":"

    Bases: Entity, Uploadable, MedperfSchema, DeployableSchema

    Class representing a Dataset

    Datasets are stored locally in the Data Owner's machine. They contain information regarding the prepared dataset, such as name and description, general statistics and an UID generated by hashing the contents of the data preparation output.

    Source code in cli/medperf/entities/dataset.py
    class Dataset(Entity, Uploadable, MedperfSchema, DeployableSchema):\n\"\"\"\n    Class representing a Dataset\n    Datasets are stored locally in the Data Owner's machine. They contain\n    information regarding the prepared dataset, such as name and description,\n    general statistics and an UID generated by hashing the contents of the\n    data preparation output.\n    \"\"\"\ndescription: Optional[str] = Field(None, max_length=20)\nlocation: Optional[str] = Field(None, max_length=20)\ninput_data_hash: str\ngenerated_uid: str\ndata_preparation_mlcube: Union[int, str]\nsplit_seed: Optional[int]\ngenerated_metadata: dict = Field(..., alias=\"metadata\")\nuser_metadata: dict = {}\nreport: dict = {}\nsubmitted_as_prepared: bool\n@validator(\"data_preparation_mlcube\", pre=True, always=True)\ndef check_data_preparation_mlcube(cls, v, *, values, **kwargs):\nif not isinstance(v, int) and not values[\"for_test\"]:\nraise ValueError(\n\"data_preparation_mlcube must be an integer if not running a compatibility test\"\n)\nreturn v\ndef __init__(self, *args, **kwargs):\nsuper().__init__(*args, **kwargs)\npath = config.datasets_folder\nif self.id:\npath = os.path.join(path, str(self.id))\nelse:\npath = os.path.join(path, self.generated_uid)\nself.path = path\nself.data_path = os.path.join(self.path, \"data\")\nself.labels_path = os.path.join(self.path, \"labels\")\nself.report_path = os.path.join(self.path, config.report_file)\nself.metadata_path = os.path.join(self.path, config.metadata_folder)\nself.statistics_path = os.path.join(self.path, config.statistics_filename)\ndef set_raw_paths(self, raw_data_path: str, raw_labels_path: str):\nraw_paths_file = os.path.join(self.path, config.dataset_raw_paths_file)\ndata = {\"data_path\": raw_data_path, \"labels_path\": raw_labels_path}\nwith open(raw_paths_file, \"w\") as f:\nyaml.dump(data, f)\ndef get_raw_paths(self):\nraw_paths_file = os.path.join(self.path, config.dataset_raw_paths_file)\nwith open(raw_paths_file) as f:\ndata = yaml.safe_load(f)\nreturn data[\"data_path\"], data[\"labels_path\"]\ndef mark_as_ready(self):\nflag_file = os.path.join(self.path, config.ready_flag_file)\nwith open(flag_file, \"w\"):\npass\ndef unmark_as_ready(self):\nflag_file = os.path.join(self.path, config.ready_flag_file)\nremove_path(flag_file)\ndef is_ready(self):\nflag_file = os.path.join(self.path, config.ready_flag_file)\nreturn os.path.exists(flag_file)\ndef todict(self):\nreturn self.extended_dict()\n@classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Dataset\"]:\n\"\"\"Gets and creates instances of all the locally prepared datasets\n        Args:\n            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n        Returns:\n            List[Dataset]: a list of Dataset instances.\n        \"\"\"\nlogging.info(\"Retrieving all datasets\")\ndsets = []\nif not local_only:\ndsets = cls.__remote_all(filters=filters)\nremote_uids = set([dset.id for dset in dsets])\nlocal_dsets = cls.__local_all()\ndsets += [dset for dset in local_dsets if dset.id not in remote_uids]\nreturn dsets\n@classmethod\ndef __remote_all(cls, filters: dict) -> List[\"Dataset\"]:\ndsets = []\ntry:\ncomms_fn = cls.__remote_prefilter(filters)\ndsets_meta = comms_fn()\ndsets = [cls(**meta) for meta in dsets_meta]\nexcept CommunicationRetrievalError:\nmsg = \"Couldn't retrieve all datasets from the server\"\nlogging.warning(msg)\nreturn dsets\n@classmethod\ndef __remote_prefilter(cls, filters: dict) -> callable:\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n        Args:\n            filters (dict): filters to apply\n        Returns:\n            callable: A function for retrieving remote entities with the applied prefilters\n        \"\"\"\ncomms_fn = config.comms.get_datasets\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_datasets\nif \"mlcube\" in filters and filters[\"mlcube\"] is not None:\ndef func():\nreturn config.comms.get_mlcube_datasets(filters[\"mlcube\"])\ncomms_fn = func\nreturn comms_fn\n@classmethod\ndef __local_all(cls) -> List[\"Dataset\"]:\ndsets = []\ndatasets_folder = config.datasets_folder\ntry:\nuids = next(os.walk(datasets_folder))[1]\nexcept StopIteration:\nmsg = \"Couldn't iterate over the dataset directory\"\nlogging.warning(msg)\nraise MedperfException(msg)\nfor uid in uids:\nlocal_meta = cls.__get_local_dict(uid)\ndset = cls(**local_meta)\ndsets.append(dset)\nreturn dsets\n@classmethod\ndef get(cls, dset_uid: Union[str, int], local_only: bool = False) -> \"Dataset\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n        If the dataset is present in the user's machine then it retrieves it from there.\n        Args:\n            dset_uid (str): server UID of the dataset\n        Returns:\n            Dataset: Specified Dataset Instance\n        \"\"\"\nif not str(dset_uid).isdigit() or local_only:\nreturn cls.__local_get(dset_uid)\ntry:\nreturn cls.__remote_get(dset_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting Dataset {dset_uid} from comms failed\")\nlogging.info(f\"Looking for dataset {dset_uid} locally\")\nreturn cls.__local_get(dset_uid)\n@classmethod\ndef __remote_get(cls, dset_uid: int) -> \"Dataset\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n        If the dataset is present in the user's machine then it retrieves it from there.\n        Args:\n            dset_uid (str): server UID of the dataset\n        Returns:\n            Dataset: Specified Dataset Instance\n        \"\"\"\nlogging.debug(f\"Retrieving dataset {dset_uid} remotely\")\nmeta = config.comms.get_dataset(dset_uid)\ndataset = cls(**meta)\ndataset.write()\nreturn dataset\n@classmethod\ndef __local_get(cls, dset_uid: Union[str, int]) -> \"Dataset\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n        If the dataset is present in the user's machine then it retrieves it from there.\n        Args:\n            dset_uid (str): server UID of the dataset\n        Returns:\n            Dataset: Specified Dataset Instance\n        \"\"\"\nlogging.debug(f\"Retrieving dataset {dset_uid} locally\")\nlocal_meta = cls.__get_local_dict(dset_uid)\ndataset = cls(**local_meta)\nreturn dataset\ndef write(self):\nlogging.info(f\"Updating registration information for dataset: {self.id}\")\nlogging.debug(f\"registration information: {self.todict()}\")\nregfile = os.path.join(self.path, config.reg_file)\nos.makedirs(self.path, exist_ok=True)\nwith open(regfile, \"w\") as f:\nyaml.dump(self.todict(), f)\nreturn regfile\ndef upload(self):\n\"\"\"Uploads the registration information to the comms.\n        Args:\n            comms (Comms): Instance of the comms interface.\n        \"\"\"\nif self.for_test:\nraise InvalidArgumentError(\"Cannot upload test datasets.\")\ndataset_dict = self.todict()\nupdated_dataset_dict = config.comms.upload_dataset(dataset_dict)\nreturn updated_dataset_dict\n@classmethod\ndef __get_local_dict(cls, data_uid):\ndataset_path = os.path.join(config.datasets_folder, str(data_uid))\nregfile = os.path.join(dataset_path, config.reg_file)\nif not os.path.exists(regfile):\nraise InvalidArgumentError(\n\"The requested dataset information could not be found locally\"\n)\nwith open(regfile, \"r\") as f:\nreg = yaml.safe_load(f)\nreturn reg\ndef display_dict(self):\nreturn {\n\"UID\": self.identifier,\n\"Name\": self.name,\n\"Description\": self.description,\n\"Location\": self.location,\n\"Data Preparation Cube UID\": self.data_preparation_mlcube,\n\"Generated Hash\": self.generated_uid,\n\"State\": self.state,\n\"Created At\": self.created_at,\n\"Registered\": self.is_registered,\n\"Submitted as Prepared\": self.submitted_as_prepared,\n\"Status\": \"\\n\".join([f\"{k}: {v}\" for k, v in self.report.items()]),\n\"Owner\": self.owner,\n}\n
    "},{"location":"reference/entities/dataset/#entities.dataset.Dataset.__local_get","title":"__local_get(dset_uid) classmethod","text":"

    Retrieves and creates a Dataset instance from the comms instance. If the dataset is present in the user's machine then it retrieves it from there.

    Parameters:

    Name Type Description Default dset_uid str

    server UID of the dataset

    required

    Returns:

    Name Type Description Dataset Dataset

    Specified Dataset Instance

    Source code in cli/medperf/entities/dataset.py
    @classmethod\ndef __local_get(cls, dset_uid: Union[str, int]) -> \"Dataset\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n    If the dataset is present in the user's machine then it retrieves it from there.\n    Args:\n        dset_uid (str): server UID of the dataset\n    Returns:\n        Dataset: Specified Dataset Instance\n    \"\"\"\nlogging.debug(f\"Retrieving dataset {dset_uid} locally\")\nlocal_meta = cls.__get_local_dict(dset_uid)\ndataset = cls(**local_meta)\nreturn dataset\n
    "},{"location":"reference/entities/dataset/#entities.dataset.Dataset.__remote_get","title":"__remote_get(dset_uid) classmethod","text":"

    Retrieves and creates a Dataset instance from the comms instance. If the dataset is present in the user's machine then it retrieves it from there.

    Parameters:

    Name Type Description Default dset_uid str

    server UID of the dataset

    required

    Returns:

    Name Type Description Dataset Dataset

    Specified Dataset Instance

    Source code in cli/medperf/entities/dataset.py
    @classmethod\ndef __remote_get(cls, dset_uid: int) -> \"Dataset\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n    If the dataset is present in the user's machine then it retrieves it from there.\n    Args:\n        dset_uid (str): server UID of the dataset\n    Returns:\n        Dataset: Specified Dataset Instance\n    \"\"\"\nlogging.debug(f\"Retrieving dataset {dset_uid} remotely\")\nmeta = config.comms.get_dataset(dset_uid)\ndataset = cls(**meta)\ndataset.write()\nreturn dataset\n
    "},{"location":"reference/entities/dataset/#entities.dataset.Dataset.__remote_prefilter","title":"__remote_prefilter(filters) classmethod","text":"

    Applies filtering logic that must be done before retrieving remote entities

    Parameters:

    Name Type Description Default filters dict

    filters to apply

    required

    Returns:

    Name Type Description callable callable

    A function for retrieving remote entities with the applied prefilters

    Source code in cli/medperf/entities/dataset.py
    @classmethod\ndef __remote_prefilter(cls, filters: dict) -> callable:\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n    Args:\n        filters (dict): filters to apply\n    Returns:\n        callable: A function for retrieving remote entities with the applied prefilters\n    \"\"\"\ncomms_fn = config.comms.get_datasets\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_datasets\nif \"mlcube\" in filters and filters[\"mlcube\"] is not None:\ndef func():\nreturn config.comms.get_mlcube_datasets(filters[\"mlcube\"])\ncomms_fn = func\nreturn comms_fn\n
    "},{"location":"reference/entities/dataset/#entities.dataset.Dataset.all","title":"all(local_only=False, filters={}) classmethod","text":"

    Gets and creates instances of all the locally prepared datasets

    Parameters:

    Name Type Description Default local_only bool

    Wether to retrieve only local entities. Defaults to False.

    False filters dict

    key-value pairs specifying filters to apply to the list of entities.

    {}

    Returns:

    Type Description List[Dataset]

    List[Dataset]: a list of Dataset instances.

    Source code in cli/medperf/entities/dataset.py
    @classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Dataset\"]:\n\"\"\"Gets and creates instances of all the locally prepared datasets\n    Args:\n        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n    Returns:\n        List[Dataset]: a list of Dataset instances.\n    \"\"\"\nlogging.info(\"Retrieving all datasets\")\ndsets = []\nif not local_only:\ndsets = cls.__remote_all(filters=filters)\nremote_uids = set([dset.id for dset in dsets])\nlocal_dsets = cls.__local_all()\ndsets += [dset for dset in local_dsets if dset.id not in remote_uids]\nreturn dsets\n
    "},{"location":"reference/entities/dataset/#entities.dataset.Dataset.get","title":"get(dset_uid, local_only=False) classmethod","text":"

    Retrieves and creates a Dataset instance from the comms instance. If the dataset is present in the user's machine then it retrieves it from there.

    Parameters:

    Name Type Description Default dset_uid str

    server UID of the dataset

    required

    Returns:

    Name Type Description Dataset Dataset

    Specified Dataset Instance

    Source code in cli/medperf/entities/dataset.py
    @classmethod\ndef get(cls, dset_uid: Union[str, int], local_only: bool = False) -> \"Dataset\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n    If the dataset is present in the user's machine then it retrieves it from there.\n    Args:\n        dset_uid (str): server UID of the dataset\n    Returns:\n        Dataset: Specified Dataset Instance\n    \"\"\"\nif not str(dset_uid).isdigit() or local_only:\nreturn cls.__local_get(dset_uid)\ntry:\nreturn cls.__remote_get(dset_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting Dataset {dset_uid} from comms failed\")\nlogging.info(f\"Looking for dataset {dset_uid} locally\")\nreturn cls.__local_get(dset_uid)\n
    "},{"location":"reference/entities/dataset/#entities.dataset.Dataset.upload","title":"upload()","text":"

    Uploads the registration information to the comms.

    Parameters:

    Name Type Description Default comms Comms

    Instance of the comms interface.

    required Source code in cli/medperf/entities/dataset.py
    def upload(self):\n\"\"\"Uploads the registration information to the comms.\n    Args:\n        comms (Comms): Instance of the comms interface.\n    \"\"\"\nif self.for_test:\nraise InvalidArgumentError(\"Cannot upload test datasets.\")\ndataset_dict = self.todict()\nupdated_dataset_dict = config.comms.upload_dataset(dataset_dict)\nreturn updated_dataset_dict\n
    "},{"location":"reference/entities/interface/","title":"Interface","text":""},{"location":"reference/entities/interface/#entities.interface.Entity","title":"Entity","text":"

    Bases: ABC

    Source code in cli/medperf/entities/interface.py
    class Entity(ABC):\n@abstractmethod\ndef all(\ncls, local_only: bool = False, comms_func: callable = None\n) -> List[\"Entity\"]:\n\"\"\"Gets a list of all instances of the respective entity.\n        Wether the list is local or remote depends on the implementation.\n        Args:\n            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n            comms_func (callable, optional): Function to use to retrieve remote entities.\n                If not provided, will use the default entrypoint.\n        Returns:\n            List[Entity]: a list of entities.\n        \"\"\"\n@abstractmethod\ndef get(cls, uid: Union[str, int]) -> \"Entity\":\n\"\"\"Gets an instance of the respective entity.\n        Wether this requires only local read or remote calls depends\n        on the implementation.\n        Args:\n            uid (str): Unique Identifier to retrieve the entity\n        Returns:\n            Entity: Entity Instance associated to the UID\n        \"\"\"\n@abstractmethod\ndef todict(self) -> Dict:\n\"\"\"Dictionary representation of the entity\n        Returns:\n            Dict: Dictionary containing information about the entity\n        \"\"\"\n@abstractmethod\ndef write(self) -> str:\n\"\"\"Writes the entity to the local storage\n        Returns:\n            str: Path to the stored entity\n        \"\"\"\n@abstractmethod\ndef display_dict(self) -> dict:\n\"\"\"Returns a dictionary of entity properties that can be displayed\n        to a user interface using a verbose name of the property rather than\n        the internal names\n        Returns:\n            dict: the display dictionary\n        \"\"\"\n
    "},{"location":"reference/entities/interface/#entities.interface.Entity.all","title":"all(local_only=False, comms_func=None) abstractmethod","text":"

    Gets a list of all instances of the respective entity. Wether the list is local or remote depends on the implementation.

    Parameters:

    Name Type Description Default local_only bool

    Wether to retrieve only local entities. Defaults to False.

    False comms_func callable

    Function to use to retrieve remote entities. If not provided, will use the default entrypoint.

    None

    Returns:

    Type Description List[Entity]

    List[Entity]: a list of entities.

    Source code in cli/medperf/entities/interface.py
    @abstractmethod\ndef all(\ncls, local_only: bool = False, comms_func: callable = None\n) -> List[\"Entity\"]:\n\"\"\"Gets a list of all instances of the respective entity.\n    Wether the list is local or remote depends on the implementation.\n    Args:\n        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n        comms_func (callable, optional): Function to use to retrieve remote entities.\n            If not provided, will use the default entrypoint.\n    Returns:\n        List[Entity]: a list of entities.\n    \"\"\"\n
    "},{"location":"reference/entities/interface/#entities.interface.Entity.display_dict","title":"display_dict() abstractmethod","text":"

    Returns a dictionary of entity properties that can be displayed to a user interface using a verbose name of the property rather than the internal names

    Returns:

    Name Type Description dict dict

    the display dictionary

    Source code in cli/medperf/entities/interface.py
    @abstractmethod\ndef display_dict(self) -> dict:\n\"\"\"Returns a dictionary of entity properties that can be displayed\n    to a user interface using a verbose name of the property rather than\n    the internal names\n    Returns:\n        dict: the display dictionary\n    \"\"\"\n
    "},{"location":"reference/entities/interface/#entities.interface.Entity.get","title":"get(uid) abstractmethod","text":"

    Gets an instance of the respective entity. Wether this requires only local read or remote calls depends on the implementation.

    Parameters:

    Name Type Description Default uid str

    Unique Identifier to retrieve the entity

    required

    Returns:

    Name Type Description Entity Entity

    Entity Instance associated to the UID

    Source code in cli/medperf/entities/interface.py
    @abstractmethod\ndef get(cls, uid: Union[str, int]) -> \"Entity\":\n\"\"\"Gets an instance of the respective entity.\n    Wether this requires only local read or remote calls depends\n    on the implementation.\n    Args:\n        uid (str): Unique Identifier to retrieve the entity\n    Returns:\n        Entity: Entity Instance associated to the UID\n    \"\"\"\n
    "},{"location":"reference/entities/interface/#entities.interface.Entity.todict","title":"todict() abstractmethod","text":"

    Dictionary representation of the entity

    Returns:

    Name Type Description Dict Dict

    Dictionary containing information about the entity

    Source code in cli/medperf/entities/interface.py
    @abstractmethod\ndef todict(self) -> Dict:\n\"\"\"Dictionary representation of the entity\n    Returns:\n        Dict: Dictionary containing information about the entity\n    \"\"\"\n
    "},{"location":"reference/entities/interface/#entities.interface.Entity.write","title":"write() abstractmethod","text":"

    Writes the entity to the local storage

    Returns:

    Name Type Description str str

    Path to the stored entity

    Source code in cli/medperf/entities/interface.py
    @abstractmethod\ndef write(self) -> str:\n\"\"\"Writes the entity to the local storage\n    Returns:\n        str: Path to the stored entity\n    \"\"\"\n
    "},{"location":"reference/entities/interface/#entities.interface.Uploadable","title":"Uploadable","text":"Source code in cli/medperf/entities/interface.py
    class Uploadable:\n@abstractmethod\ndef upload(self) -> Dict:\n\"\"\"Upload the entity-related information to the communication's interface\n        Returns:\n            Dict: Dictionary with the updated entity information\n        \"\"\"\n@property\ndef identifier(self):\nreturn self.id or self.generated_uid\n@property\ndef is_registered(self):\nreturn self.id is not None\n
    "},{"location":"reference/entities/interface/#entities.interface.Uploadable.upload","title":"upload() abstractmethod","text":"

    Upload the entity-related information to the communication's interface

    Returns:

    Name Type Description Dict Dict

    Dictionary with the updated entity information

    Source code in cli/medperf/entities/interface.py
    @abstractmethod\ndef upload(self) -> Dict:\n\"\"\"Upload the entity-related information to the communication's interface\n    Returns:\n        Dict: Dictionary with the updated entity information\n    \"\"\"\n
    "},{"location":"reference/entities/report/","title":"Report","text":""},{"location":"reference/entities/report/#entities.report.TestReport","title":"TestReport","text":"

    Bases: Entity, MedperfBaseSchema

    Class representing a compatibility test report entry

    A test report consists of the components of a test execution: - data used, which can be: - a demo dataset url and its hash, or - a raw data path and its labels path, or - a prepared dataset uid - Data preparation cube if the data used was not already prepared - model cube - evaluator cube - results

    Source code in cli/medperf/entities/report.py
    class TestReport(Entity, MedperfBaseSchema):\n\"\"\"\n    Class representing a compatibility test report entry\n    A test report consists of the components of a test execution:\n    - data used, which can be:\n        - a demo dataset url and its hash, or\n        - a raw data path and its labels path, or\n        - a prepared dataset uid\n    - Data preparation cube if the data used was not already prepared\n    - model cube\n    - evaluator cube\n    - results\n    \"\"\"\ndemo_dataset_url: Optional[str]\ndemo_dataset_hash: Optional[str]\ndata_path: Optional[str]\nlabels_path: Optional[str]\nprepared_data_hash: Optional[str]\ndata_preparation_mlcube: Optional[Union[int, str]]\nmodel: Union[int, str]\ndata_evaluator_mlcube: Union[int, str]\nresults: Optional[dict]\ndef __init__(self, *args, **kwargs):\nsuper().__init__(*args, **kwargs)\nself.generated_uid = self.__generate_uid()\npath = config.tests_folder\nself.path = os.path.join(path, self.generated_uid)\ndef __generate_uid(self):\n\"\"\"A helper that generates a unique hash for a test report.\"\"\"\nparams = self.todict()\ndel params[\"results\"]\nparams = str(params)\nreturn hashlib.sha256(params.encode()).hexdigest()\ndef set_results(self, results):\nself.results = results\n@classmethod\ndef all(\ncls, local_only: bool = False, mine_only: bool = False\n) -> List[\"TestReport\"]:\n\"\"\"Gets and creates instances of test reports.\n        Arguments are only specified for compatibility with\n        `Entity.List` and `Entity.View`, but they don't contribute to\n        the logic.\n        Returns:\n            List[TestReport]: List containing all test reports\n        \"\"\"\nlogging.info(\"Retrieving all reports\")\nreports = []\ntests_folder = config.tests_folder\ntry:\nuids = next(os.walk(tests_folder))[1]\nexcept StopIteration:\nmsg = \"Couldn't iterate over the tests directory\"\nlogging.warning(msg)\nraise RuntimeError(msg)\nfor uid in uids:\nlocal_meta = cls.__get_local_dict(uid)\nreport = cls(**local_meta)\nreports.append(report)\nreturn reports\n@classmethod\ndef get(cls, report_uid: str) -> \"TestReport\":\n\"\"\"Retrieves and creates a TestReport instance obtained the user's machine\n        Args:\n            report_uid (str): UID of the TestReport instance\n        Returns:\n            TestReport: Specified TestReport instance\n        \"\"\"\nlogging.debug(f\"Retrieving report {report_uid}\")\nreport_dict = cls.__get_local_dict(report_uid)\nreport = cls(**report_dict)\nreport.write()\nreturn report\ndef todict(self):\nreturn self.extended_dict()\ndef write(self):\nreport_file = os.path.join(self.path, config.test_report_file)\nos.makedirs(self.path, exist_ok=True)\nwith open(report_file, \"w\") as f:\nyaml.dump(self.todict(), f)\nreturn report_file\n@classmethod\ndef __get_local_dict(cls, local_uid):\nreport_path = os.path.join(config.tests_folder, str(local_uid))\nreport_file = os.path.join(report_path, config.test_report_file)\nif not os.path.exists(report_file):\nraise InvalidArgumentError(\nf\"The requested report {local_uid} could not be retrieved\"\n)\nwith open(report_file, \"r\") as f:\nreport_info = yaml.safe_load(f)\nreturn report_info\ndef display_dict(self):\nif self.data_path:\ndata_source = f\"{self.data_path}\"[:27] + \"...\"\nelif self.demo_dataset_url:\ndata_source = f\"{self.demo_dataset_url}\"[:27] + \"...\"\nelse:\ndata_source = f\"{self.prepared_data_hash}\"\nreturn {\n\"UID\": self.generated_uid,\n\"Data Source\": data_source,\n\"Model\": (\nself.model if isinstance(self.model, int) else self.model[:27] + \"...\"\n),\n\"Evaluator\": (\nself.data_evaluator_mlcube\nif isinstance(self.data_evaluator_mlcube, int)\nelse self.data_evaluator_mlcube[:27] + \"...\"\n),\n}\n
    "},{"location":"reference/entities/report/#entities.report.TestReport.__generate_uid","title":"__generate_uid()","text":"

    A helper that generates a unique hash for a test report.

    Source code in cli/medperf/entities/report.py
    def __generate_uid(self):\n\"\"\"A helper that generates a unique hash for a test report.\"\"\"\nparams = self.todict()\ndel params[\"results\"]\nparams = str(params)\nreturn hashlib.sha256(params.encode()).hexdigest()\n
    "},{"location":"reference/entities/report/#entities.report.TestReport.all","title":"all(local_only=False, mine_only=False) classmethod","text":"

    Gets and creates instances of test reports. Arguments are only specified for compatibility with Entity.List and Entity.View, but they don't contribute to the logic.

    Returns:

    Type Description List[TestReport]

    List[TestReport]: List containing all test reports

    Source code in cli/medperf/entities/report.py
    @classmethod\ndef all(\ncls, local_only: bool = False, mine_only: bool = False\n) -> List[\"TestReport\"]:\n\"\"\"Gets and creates instances of test reports.\n    Arguments are only specified for compatibility with\n    `Entity.List` and `Entity.View`, but they don't contribute to\n    the logic.\n    Returns:\n        List[TestReport]: List containing all test reports\n    \"\"\"\nlogging.info(\"Retrieving all reports\")\nreports = []\ntests_folder = config.tests_folder\ntry:\nuids = next(os.walk(tests_folder))[1]\nexcept StopIteration:\nmsg = \"Couldn't iterate over the tests directory\"\nlogging.warning(msg)\nraise RuntimeError(msg)\nfor uid in uids:\nlocal_meta = cls.__get_local_dict(uid)\nreport = cls(**local_meta)\nreports.append(report)\nreturn reports\n
    "},{"location":"reference/entities/report/#entities.report.TestReport.get","title":"get(report_uid) classmethod","text":"

    Retrieves and creates a TestReport instance obtained the user's machine

    Parameters:

    Name Type Description Default report_uid str

    UID of the TestReport instance

    required

    Returns:

    Name Type Description TestReport TestReport

    Specified TestReport instance

    Source code in cli/medperf/entities/report.py
    @classmethod\ndef get(cls, report_uid: str) -> \"TestReport\":\n\"\"\"Retrieves and creates a TestReport instance obtained the user's machine\n    Args:\n        report_uid (str): UID of the TestReport instance\n    Returns:\n        TestReport: Specified TestReport instance\n    \"\"\"\nlogging.debug(f\"Retrieving report {report_uid}\")\nreport_dict = cls.__get_local_dict(report_uid)\nreport = cls(**report_dict)\nreport.write()\nreturn report\n
    "},{"location":"reference/entities/result/","title":"Result","text":""},{"location":"reference/entities/result/#entities.result.Result","title":"Result","text":"

    Bases: Entity, Uploadable, MedperfSchema, ApprovableSchema

    Class representing a Result entry

    Results are obtained after successfully running a benchmark execution flow. They contain information regarding the components involved in obtaining metrics results, as well as the results themselves. This class provides methods for working with benchmark results and how to upload them to the backend.

    Source code in cli/medperf/entities/result.py
    class Result(Entity, Uploadable, MedperfSchema, ApprovableSchema):\n\"\"\"\n    Class representing a Result entry\n    Results are obtained after successfully running a benchmark\n    execution flow. They contain information regarding the\n    components involved in obtaining metrics results, as well as the\n    results themselves. This class provides methods for working with\n    benchmark results and how to upload them to the backend.\n    \"\"\"\nbenchmark: int\nmodel: int\ndataset: int\nresults: dict\nmetadata: dict = {}\nuser_metadata: dict = {}\ndef __init__(self, *args, **kwargs):\n\"\"\"Creates a new result instance\"\"\"\nsuper().__init__(*args, **kwargs)\nself.generated_uid = f\"b{self.benchmark}m{self.model}d{self.dataset}\"\npath = config.results_folder\nif self.id:\npath = os.path.join(path, str(self.id))\nelse:\npath = os.path.join(path, self.generated_uid)\nself.path = path\n@classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Result\"]:\n\"\"\"Gets and creates instances of all the user's results\n        Args:\n            local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n            filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n        Returns:\n            List[Result]: List containing all results\n        \"\"\"\nlogging.info(\"Retrieving all results\")\nresults = []\nif not local_only:\nresults = cls.__remote_all(filters=filters)\nremote_uids = set([result.id for result in results])\nlocal_results = cls.__local_all()\nresults += [res for res in local_results if res.id not in remote_uids]\nreturn results\n@classmethod\ndef __remote_all(cls, filters: dict) -> List[\"Result\"]:\nresults = []\ntry:\ncomms_fn = cls.__remote_prefilter(filters)\nresults_meta = comms_fn()\nresults = [cls(**meta) for meta in results_meta]\nexcept CommunicationRetrievalError:\nmsg = \"Couldn't retrieve all results from the server\"\nlogging.warning(msg)\nreturn results\n@classmethod\ndef __remote_prefilter(cls, filters: dict) -> callable:\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n        Args:\n            filters (dict): filters to apply\n        Returns:\n            callable: A function for retrieving remote entities with the applied prefilters\n        \"\"\"\ncomms_fn = config.comms.get_results\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_results\nif \"benchmark\" in filters and filters[\"benchmark\"] is not None:\nbmk = filters[\"benchmark\"]\ndef get_benchmark_results():\n# Decorate the benchmark results remote function so it has the same signature\n# as all the comms_fns\nreturn config.comms.get_benchmark_results(bmk)\ncomms_fn = get_benchmark_results\nreturn comms_fn\n@classmethod\ndef __local_all(cls) -> List[\"Result\"]:\nresults = []\nresults_folder = config.results_folder\ntry:\nuids = next(os.walk(results_folder))[1]\nexcept StopIteration:\nmsg = \"Couldn't iterate over the dataset directory\"\nlogging.warning(msg)\nraise RuntimeError(msg)\nfor uid in uids:\nlocal_meta = cls.__get_local_dict(uid)\nresult = cls(**local_meta)\nresults.append(result)\nreturn results\n@classmethod\ndef get(cls, result_uid: Union[str, int], local_only: bool = False) -> \"Result\":\n\"\"\"Retrieves and creates a Result instance obtained from the platform.\n        If the result instance already exists in the user's machine, it loads\n        the local instance\n        Args:\n            result_uid (str): UID of the Result instance\n        Returns:\n            Result: Specified Result instance\n        \"\"\"\nif not str(result_uid).isdigit() or local_only:\nreturn cls.__local_get(result_uid)\ntry:\nreturn cls.__remote_get(result_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting Result {result_uid} from comms failed\")\nlogging.info(f\"Looking for result {result_uid} locally\")\nreturn cls.__local_get(result_uid)\n@classmethod\ndef __remote_get(cls, result_uid: int) -> \"Result\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n        If the dataset is present in the user's machine then it retrieves it from there.\n        Args:\n            result_uid (str): server UID of the dataset\n        Returns:\n            Dataset: Specified Dataset Instance\n        \"\"\"\nlogging.debug(f\"Retrieving result {result_uid} remotely\")\nmeta = config.comms.get_result(result_uid)\nresult = cls(**meta)\nresult.write()\nreturn result\n@classmethod\ndef __local_get(cls, result_uid: Union[str, int]) -> \"Result\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n        If the dataset is present in the user's machine then it retrieves it from there.\n        Args:\n            result_uid (str): server UID of the dataset\n        Returns:\n            Dataset: Specified Dataset Instance\n        \"\"\"\nlogging.debug(f\"Retrieving result {result_uid} locally\")\nlocal_meta = cls.__get_local_dict(result_uid)\nresult = cls(**local_meta)\nreturn result\ndef todict(self):\nreturn self.extended_dict()\ndef upload(self):\n\"\"\"Uploads the results to the comms\n        Args:\n            comms (Comms): Instance of the communications interface.\n        \"\"\"\nif self.for_test:\nraise InvalidArgumentError(\"Cannot upload test results.\")\nresults_info = self.todict()\nupdated_results_info = config.comms.upload_result(results_info)\nreturn updated_results_info\ndef write(self):\nresult_file = os.path.join(self.path, config.results_info_file)\nos.makedirs(self.path, exist_ok=True)\nwith open(result_file, \"w\") as f:\nyaml.dump(self.todict(), f)\nreturn result_file\n@classmethod\ndef __get_local_dict(cls, local_uid):\nresult_path = os.path.join(config.results_folder, str(local_uid))\nresult_file = os.path.join(result_path, config.results_info_file)\nif not os.path.exists(result_file):\nraise InvalidArgumentError(\nf\"The requested result {local_uid} could not be retrieved\"\n)\nwith open(result_file, \"r\") as f:\nresults_info = yaml.safe_load(f)\nreturn results_info\ndef display_dict(self):\nreturn {\n\"UID\": self.identifier,\n\"Name\": self.name,\n\"Benchmark\": self.benchmark,\n\"Model\": self.model,\n\"Dataset\": self.dataset,\n\"Partial\": self.metadata[\"partial\"],\n\"Approval Status\": self.approval_status,\n\"Created At\": self.created_at,\n\"Registered\": self.is_registered,\n}\n
    "},{"location":"reference/entities/result/#entities.result.Result.__init__","title":"__init__(*args, **kwargs)","text":"

    Creates a new result instance

    Source code in cli/medperf/entities/result.py
    def __init__(self, *args, **kwargs):\n\"\"\"Creates a new result instance\"\"\"\nsuper().__init__(*args, **kwargs)\nself.generated_uid = f\"b{self.benchmark}m{self.model}d{self.dataset}\"\npath = config.results_folder\nif self.id:\npath = os.path.join(path, str(self.id))\nelse:\npath = os.path.join(path, self.generated_uid)\nself.path = path\n
    "},{"location":"reference/entities/result/#entities.result.Result.__local_get","title":"__local_get(result_uid) classmethod","text":"

    Retrieves and creates a Dataset instance from the comms instance. If the dataset is present in the user's machine then it retrieves it from there.

    Parameters:

    Name Type Description Default result_uid str

    server UID of the dataset

    required

    Returns:

    Name Type Description Dataset Result

    Specified Dataset Instance

    Source code in cli/medperf/entities/result.py
    @classmethod\ndef __local_get(cls, result_uid: Union[str, int]) -> \"Result\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n    If the dataset is present in the user's machine then it retrieves it from there.\n    Args:\n        result_uid (str): server UID of the dataset\n    Returns:\n        Dataset: Specified Dataset Instance\n    \"\"\"\nlogging.debug(f\"Retrieving result {result_uid} locally\")\nlocal_meta = cls.__get_local_dict(result_uid)\nresult = cls(**local_meta)\nreturn result\n
    "},{"location":"reference/entities/result/#entities.result.Result.__remote_get","title":"__remote_get(result_uid) classmethod","text":"

    Retrieves and creates a Dataset instance from the comms instance. If the dataset is present in the user's machine then it retrieves it from there.

    Parameters:

    Name Type Description Default result_uid str

    server UID of the dataset

    required

    Returns:

    Name Type Description Dataset Result

    Specified Dataset Instance

    Source code in cli/medperf/entities/result.py
    @classmethod\ndef __remote_get(cls, result_uid: int) -> \"Result\":\n\"\"\"Retrieves and creates a Dataset instance from the comms instance.\n    If the dataset is present in the user's machine then it retrieves it from there.\n    Args:\n        result_uid (str): server UID of the dataset\n    Returns:\n        Dataset: Specified Dataset Instance\n    \"\"\"\nlogging.debug(f\"Retrieving result {result_uid} remotely\")\nmeta = config.comms.get_result(result_uid)\nresult = cls(**meta)\nresult.write()\nreturn result\n
    "},{"location":"reference/entities/result/#entities.result.Result.__remote_prefilter","title":"__remote_prefilter(filters) classmethod","text":"

    Applies filtering logic that must be done before retrieving remote entities

    Parameters:

    Name Type Description Default filters dict

    filters to apply

    required

    Returns:

    Name Type Description callable callable

    A function for retrieving remote entities with the applied prefilters

    Source code in cli/medperf/entities/result.py
    @classmethod\ndef __remote_prefilter(cls, filters: dict) -> callable:\n\"\"\"Applies filtering logic that must be done before retrieving remote entities\n    Args:\n        filters (dict): filters to apply\n    Returns:\n        callable: A function for retrieving remote entities with the applied prefilters\n    \"\"\"\ncomms_fn = config.comms.get_results\nif \"owner\" in filters and filters[\"owner\"] == get_medperf_user_data()[\"id\"]:\ncomms_fn = config.comms.get_user_results\nif \"benchmark\" in filters and filters[\"benchmark\"] is not None:\nbmk = filters[\"benchmark\"]\ndef get_benchmark_results():\n# Decorate the benchmark results remote function so it has the same signature\n# as all the comms_fns\nreturn config.comms.get_benchmark_results(bmk)\ncomms_fn = get_benchmark_results\nreturn comms_fn\n
    "},{"location":"reference/entities/result/#entities.result.Result.all","title":"all(local_only=False, filters={}) classmethod","text":"

    Gets and creates instances of all the user's results

    Parameters:

    Name Type Description Default local_only bool

    Wether to retrieve only local entities. Defaults to False.

    False filters dict

    key-value pairs specifying filters to apply to the list of entities.

    {}

    Returns:

    Type Description List[Result]

    List[Result]: List containing all results

    Source code in cli/medperf/entities/result.py
    @classmethod\ndef all(cls, local_only: bool = False, filters: dict = {}) -> List[\"Result\"]:\n\"\"\"Gets and creates instances of all the user's results\n    Args:\n        local_only (bool, optional): Wether to retrieve only local entities. Defaults to False.\n        filters (dict, optional): key-value pairs specifying filters to apply to the list of entities.\n    Returns:\n        List[Result]: List containing all results\n    \"\"\"\nlogging.info(\"Retrieving all results\")\nresults = []\nif not local_only:\nresults = cls.__remote_all(filters=filters)\nremote_uids = set([result.id for result in results])\nlocal_results = cls.__local_all()\nresults += [res for res in local_results if res.id not in remote_uids]\nreturn results\n
    "},{"location":"reference/entities/result/#entities.result.Result.get","title":"get(result_uid, local_only=False) classmethod","text":"

    Retrieves and creates a Result instance obtained from the platform. If the result instance already exists in the user's machine, it loads the local instance

    Parameters:

    Name Type Description Default result_uid str

    UID of the Result instance

    required

    Returns:

    Name Type Description Result Result

    Specified Result instance

    Source code in cli/medperf/entities/result.py
    @classmethod\ndef get(cls, result_uid: Union[str, int], local_only: bool = False) -> \"Result\":\n\"\"\"Retrieves and creates a Result instance obtained from the platform.\n    If the result instance already exists in the user's machine, it loads\n    the local instance\n    Args:\n        result_uid (str): UID of the Result instance\n    Returns:\n        Result: Specified Result instance\n    \"\"\"\nif not str(result_uid).isdigit() or local_only:\nreturn cls.__local_get(result_uid)\ntry:\nreturn cls.__remote_get(result_uid)\nexcept CommunicationRetrievalError:\nlogging.warning(f\"Getting Result {result_uid} from comms failed\")\nlogging.info(f\"Looking for result {result_uid} locally\")\nreturn cls.__local_get(result_uid)\n
    "},{"location":"reference/entities/result/#entities.result.Result.upload","title":"upload()","text":"

    Uploads the results to the comms

    Parameters:

    Name Type Description Default comms Comms

    Instance of the communications interface.

    required Source code in cli/medperf/entities/result.py
    def upload(self):\n\"\"\"Uploads the results to the comms\n    Args:\n        comms (Comms): Instance of the communications interface.\n    \"\"\"\nif self.for_test:\nraise InvalidArgumentError(\"Cannot upload test results.\")\nresults_info = self.todict()\nupdated_results_info = config.comms.upload_result(results_info)\nreturn updated_results_info\n
    "},{"location":"reference/entities/schemas/","title":"Schemas","text":""},{"location":"reference/entities/schemas/#entities.schemas.MedperfBaseSchema","title":"MedperfBaseSchema","text":"

    Bases: BaseModel

    Source code in cli/medperf/entities/schemas.py
    class MedperfBaseSchema(BaseModel):\ndef __init__(self, *args, **kwargs):\n\"\"\"Override the ValidationError procedure so we can\n        format the error message in our desired way\n        \"\"\"\ntry:\nsuper().__init__(*args, **kwargs)\nexcept ValidationError as e:\nerrors_dict = defaultdict(list)\nfor error in e.errors():\nfield = error[\"loc\"]\nmsg = error[\"msg\"]\nerrors_dict[field].append(msg)\nerror_msg = \"Field Validation Error:\"\nerror_msg += format_errors_dict(errors_dict)\nraise MedperfException(error_msg)\ndef dict(self, *args, **kwargs) -> dict:\n\"\"\"Overrides dictionary implementation so it filters out\n        fields not defined in the pydantic model\n        Returns:\n            dict: filtered dictionary\n        \"\"\"\nfields = self.__fields__\nvalid_fields = []\n# Gather all the field names, both original an alias names\nfor field_name, field_item in fields.items():\nvalid_fields.append(field_name)\nvalid_fields.append(field_item.alias)\n# Remove duplicates\nvalid_fields = set(valid_fields)\nmodel_dict = super().dict(*args, **kwargs)\nout_dict = {k: v for k, v in model_dict.items() if k in valid_fields}\nreturn out_dict\ndef extended_dict(self) -> dict:\n\"\"\"Dictionary containing both original and alias fields\n        Returns:\n            dict: Extended dictionary representation\n        \"\"\"\nog_dict = self.dict()\nalias_dict = self.dict(by_alias=True)\nog_dict.update(alias_dict)\nfor k, v in og_dict.items():\nif v is None:\nog_dict[k] = \"\"\nif isinstance(v, HttpUrl):\nog_dict[k] = str(v)\nreturn og_dict\n@validator(\"*\", pre=True)\ndef empty_str_to_none(cls, v):\nif v == \"\":\nreturn None\nreturn v\nclass Config:\nallow_population_by_field_name = True\nextra = \"allow\"\nuse_enum_values = True\n
    "},{"location":"reference/entities/schemas/#entities.schemas.MedperfBaseSchema.__init__","title":"__init__(*args, **kwargs)","text":"

    Override the ValidationError procedure so we can format the error message in our desired way

    Source code in cli/medperf/entities/schemas.py
    def __init__(self, *args, **kwargs):\n\"\"\"Override the ValidationError procedure so we can\n    format the error message in our desired way\n    \"\"\"\ntry:\nsuper().__init__(*args, **kwargs)\nexcept ValidationError as e:\nerrors_dict = defaultdict(list)\nfor error in e.errors():\nfield = error[\"loc\"]\nmsg = error[\"msg\"]\nerrors_dict[field].append(msg)\nerror_msg = \"Field Validation Error:\"\nerror_msg += format_errors_dict(errors_dict)\nraise MedperfException(error_msg)\n
    "},{"location":"reference/entities/schemas/#entities.schemas.MedperfBaseSchema.dict","title":"dict(*args, **kwargs)","text":"

    Overrides dictionary implementation so it filters out fields not defined in the pydantic model

    Returns:

    Name Type Description dict dict

    filtered dictionary

    Source code in cli/medperf/entities/schemas.py
    def dict(self, *args, **kwargs) -> dict:\n\"\"\"Overrides dictionary implementation so it filters out\n    fields not defined in the pydantic model\n    Returns:\n        dict: filtered dictionary\n    \"\"\"\nfields = self.__fields__\nvalid_fields = []\n# Gather all the field names, both original an alias names\nfor field_name, field_item in fields.items():\nvalid_fields.append(field_name)\nvalid_fields.append(field_item.alias)\n# Remove duplicates\nvalid_fields = set(valid_fields)\nmodel_dict = super().dict(*args, **kwargs)\nout_dict = {k: v for k, v in model_dict.items() if k in valid_fields}\nreturn out_dict\n
    "},{"location":"reference/entities/schemas/#entities.schemas.MedperfBaseSchema.extended_dict","title":"extended_dict()","text":"

    Dictionary containing both original and alias fields

    Returns:

    Name Type Description dict dict

    Extended dictionary representation

    Source code in cli/medperf/entities/schemas.py
    def extended_dict(self) -> dict:\n\"\"\"Dictionary containing both original and alias fields\n    Returns:\n        dict: Extended dictionary representation\n    \"\"\"\nog_dict = self.dict()\nalias_dict = self.dict(by_alias=True)\nog_dict.update(alias_dict)\nfor k, v in og_dict.items():\nif v is None:\nog_dict[k] = \"\"\nif isinstance(v, HttpUrl):\nog_dict[k] = str(v)\nreturn og_dict\n
    "},{"location":"reference/storage/utils/","title":"Utils","text":""},{"location":"reference/ui/cli/","title":"Cli","text":""},{"location":"reference/ui/cli/#ui.cli.CLI","title":"CLI","text":"

    Bases: UI

    Source code in cli/medperf/ui/cli.py
    class CLI(UI):\ndef __init__(self):\nself.spinner = yaspin(color=\"green\")\nself.is_interactive = False\ndef print(self, msg: str = \"\"):\n\"\"\"Display a message on the command line\n        Args:\n            msg (str): message to print\n        \"\"\"\nself.__print(msg)\ndef print_error(self, msg: str):\n\"\"\"Display an error message on the command line\n        Args:\n            msg (str): error message to display\n        \"\"\"\nmsg = f\"\u274c {msg}\"\nmsg = typer.style(msg, fg=typer.colors.RED, bold=True)\nself.__print(msg)\ndef print_warning(self, msg: str):\n\"\"\"Display a warning message on the command line\n        Args:\n            msg (str): warning message to display\n        \"\"\"\nmsg = typer.style(msg, fg=typer.colors.YELLOW, bold=True)\nself.__print(msg)\ndef __print(self, msg: str = \"\"):\nif self.is_interactive:\nself.spinner.write(msg)\nelse:\ntyper.echo(msg)\ndef start_interactive(self):\n\"\"\"Start an interactive session where messages can be overwritten\n        and animations can be displayed\"\"\"\nself.is_interactive = True\nself.spinner.start()\ndef stop_interactive(self):\n\"\"\"Stop an interactive session\"\"\"\nself.is_interactive = False\nself.spinner.stop()\n@contextmanager\ndef interactive(self):\n\"\"\"Context managed interactive session.\n        Yields:\n            CLI: Yields the current CLI instance with an interactive session initialized\n        \"\"\"\nself.start_interactive()\ntry:\nyield self\nfinally:\nself.stop_interactive()\n@property\ndef text(self):\nreturn self.spinner.text\n@text.setter\ndef text(self, msg: str = \"\"):\n\"\"\"Displays a message that overwrites previous messages if they\n        were created during an interactive ui session.\n        If not on interactive session already, then it calls the ui print function\n        Args:\n            msg (str): message to display\n        \"\"\"\nif not self.is_interactive:\nself.print(msg)\nself.spinner.text = msg\ndef prompt(self, msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an answer\n        Args:\n            msg (str): message to use for the prompt\n        Returns:\n            str: user input\n        \"\"\"\nreturn input(msg)\ndef hidden_prompt(self, msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an aswer. User input is not displayed\n        Args:\n            msg (str): message to use for the prompt\n        Returns:\n            str: user input\n        \"\"\"\nreturn getpass(msg)\ndef print_highlight(self, msg: str = \"\"):\n\"\"\"Display a highlighted message\n        Args:\n            msg (str): message to print\n        \"\"\"\nmsg = typer.style(msg, fg=typer.colors.GREEN)\nself.__print(msg)\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.hidden_prompt","title":"hidden_prompt(msg)","text":"

    Displays a prompt to the user and waits for an aswer. User input is not displayed

    Parameters:

    Name Type Description Default msg str

    message to use for the prompt

    required

    Returns:

    Name Type Description str str

    user input

    Source code in cli/medperf/ui/cli.py
    def hidden_prompt(self, msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an aswer. User input is not displayed\n    Args:\n        msg (str): message to use for the prompt\n    Returns:\n        str: user input\n    \"\"\"\nreturn getpass(msg)\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.interactive","title":"interactive()","text":"

    Context managed interactive session.

    Yields:

    Name Type Description CLI

    Yields the current CLI instance with an interactive session initialized

    Source code in cli/medperf/ui/cli.py
    @contextmanager\ndef interactive(self):\n\"\"\"Context managed interactive session.\n    Yields:\n        CLI: Yields the current CLI instance with an interactive session initialized\n    \"\"\"\nself.start_interactive()\ntry:\nyield self\nfinally:\nself.stop_interactive()\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.print","title":"print(msg='')","text":"

    Display a message on the command line

    Parameters:

    Name Type Description Default msg str

    message to print

    '' Source code in cli/medperf/ui/cli.py
    def print(self, msg: str = \"\"):\n\"\"\"Display a message on the command line\n    Args:\n        msg (str): message to print\n    \"\"\"\nself.__print(msg)\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.print_error","title":"print_error(msg)","text":"

    Display an error message on the command line

    Parameters:

    Name Type Description Default msg str

    error message to display

    required Source code in cli/medperf/ui/cli.py
    def print_error(self, msg: str):\n\"\"\"Display an error message on the command line\n    Args:\n        msg (str): error message to display\n    \"\"\"\nmsg = f\"\u274c {msg}\"\nmsg = typer.style(msg, fg=typer.colors.RED, bold=True)\nself.__print(msg)\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.print_highlight","title":"print_highlight(msg='')","text":"

    Display a highlighted message

    Parameters:

    Name Type Description Default msg str

    message to print

    '' Source code in cli/medperf/ui/cli.py
    def print_highlight(self, msg: str = \"\"):\n\"\"\"Display a highlighted message\n    Args:\n        msg (str): message to print\n    \"\"\"\nmsg = typer.style(msg, fg=typer.colors.GREEN)\nself.__print(msg)\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.print_warning","title":"print_warning(msg)","text":"

    Display a warning message on the command line

    Parameters:

    Name Type Description Default msg str

    warning message to display

    required Source code in cli/medperf/ui/cli.py
    def print_warning(self, msg: str):\n\"\"\"Display a warning message on the command line\n    Args:\n        msg (str): warning message to display\n    \"\"\"\nmsg = typer.style(msg, fg=typer.colors.YELLOW, bold=True)\nself.__print(msg)\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.prompt","title":"prompt(msg)","text":"

    Displays a prompt to the user and waits for an answer

    Parameters:

    Name Type Description Default msg str

    message to use for the prompt

    required

    Returns:

    Name Type Description str str

    user input

    Source code in cli/medperf/ui/cli.py
    def prompt(self, msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an answer\n    Args:\n        msg (str): message to use for the prompt\n    Returns:\n        str: user input\n    \"\"\"\nreturn input(msg)\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.start_interactive","title":"start_interactive()","text":"

    Start an interactive session where messages can be overwritten and animations can be displayed

    Source code in cli/medperf/ui/cli.py
    def start_interactive(self):\n\"\"\"Start an interactive session where messages can be overwritten\n    and animations can be displayed\"\"\"\nself.is_interactive = True\nself.spinner.start()\n
    "},{"location":"reference/ui/cli/#ui.cli.CLI.stop_interactive","title":"stop_interactive()","text":"

    Stop an interactive session

    Source code in cli/medperf/ui/cli.py
    def stop_interactive(self):\n\"\"\"Stop an interactive session\"\"\"\nself.is_interactive = False\nself.spinner.stop()\n
    "},{"location":"reference/ui/factory/","title":"Factory","text":""},{"location":"reference/ui/interface/","title":"Interface","text":""},{"location":"reference/ui/interface/#ui.interface.UI","title":"UI","text":"

    Bases: ABC

    Source code in cli/medperf/ui/interface.py
    class UI(ABC):\n@abstractmethod\ndef print(self, msg: str = \"\"):\n\"\"\"Display a message to the interface. If on interactive session overrides\n        previous message\n        \"\"\"\n@abstractmethod\ndef print_error(self, msg: str):\n\"\"\"Display an error message to the interface\"\"\"\ndef print_warning(self, msg: str):\n\"\"\"Display a warning message on the command line\"\"\"\n@abstractmethod\ndef start_interactive(self):\n\"\"\"Initialize an interactive session for animations or overriding messages.\n        If the UI doesn't support this, the function can be left empty.\n        \"\"\"\n@abstractmethod\ndef stop_interactive(self):\n\"\"\"Terminate an interactive session.\n        If the UI doesn't support this, the function can be left empty.\n        \"\"\"\n@abstractmethod\n@contextmanager\ndef interactive(self):\n\"\"\"Context managed interactive session. Expected to yield the same instance\"\"\"\n@abstractmethod\ndef text(self, msg: str):\n\"\"\"Displays a messages that overwrites previous messages if they were created\n        during an interactive session.\n        If not supported or not on an interactive session, it is expected to fallback\n        to the UI print function.\n        Args:\n            msg (str): message to display\n        \"\"\"\n@abstractmethod\ndef prompt(msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an answer\"\"\"\n@abstractmethod\ndef hidden_prompt(self, msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an aswer. User input is not displayed\n        Args:\n            msg (str): message to use for the prompt\n        Returns:\n            str: user input\n        \"\"\"\n@abstractmethod\ndef print_highlight(self, msg: str = \"\"):\n\"\"\"Display a message on the command line with green color\n        Args:\n            msg (str): message to print\n        \"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.hidden_prompt","title":"hidden_prompt(msg) abstractmethod","text":"

    Displays a prompt to the user and waits for an aswer. User input is not displayed

    Parameters:

    Name Type Description Default msg str

    message to use for the prompt

    required

    Returns:

    Name Type Description str str

    user input

    Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef hidden_prompt(self, msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an aswer. User input is not displayed\n    Args:\n        msg (str): message to use for the prompt\n    Returns:\n        str: user input\n    \"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.interactive","title":"interactive() abstractmethod","text":"

    Context managed interactive session. Expected to yield the same instance

    Source code in cli/medperf/ui/interface.py
    @abstractmethod\n@contextmanager\ndef interactive(self):\n\"\"\"Context managed interactive session. Expected to yield the same instance\"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.print","title":"print(msg='') abstractmethod","text":"

    Display a message to the interface. If on interactive session overrides previous message

    Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef print(self, msg: str = \"\"):\n\"\"\"Display a message to the interface. If on interactive session overrides\n    previous message\n    \"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.print_error","title":"print_error(msg) abstractmethod","text":"

    Display an error message to the interface

    Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef print_error(self, msg: str):\n\"\"\"Display an error message to the interface\"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.print_highlight","title":"print_highlight(msg='') abstractmethod","text":"

    Display a message on the command line with green color

    Parameters:

    Name Type Description Default msg str

    message to print

    '' Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef print_highlight(self, msg: str = \"\"):\n\"\"\"Display a message on the command line with green color\n    Args:\n        msg (str): message to print\n    \"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.print_warning","title":"print_warning(msg)","text":"

    Display a warning message on the command line

    Source code in cli/medperf/ui/interface.py
    def print_warning(self, msg: str):\n\"\"\"Display a warning message on the command line\"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.prompt","title":"prompt(msg) abstractmethod","text":"

    Displays a prompt to the user and waits for an answer

    Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef prompt(msg: str) -> str:\n\"\"\"Displays a prompt to the user and waits for an answer\"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.start_interactive","title":"start_interactive() abstractmethod","text":"

    Initialize an interactive session for animations or overriding messages. If the UI doesn't support this, the function can be left empty.

    Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef start_interactive(self):\n\"\"\"Initialize an interactive session for animations or overriding messages.\n    If the UI doesn't support this, the function can be left empty.\n    \"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.stop_interactive","title":"stop_interactive() abstractmethod","text":"

    Terminate an interactive session. If the UI doesn't support this, the function can be left empty.

    Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef stop_interactive(self):\n\"\"\"Terminate an interactive session.\n    If the UI doesn't support this, the function can be left empty.\n    \"\"\"\n
    "},{"location":"reference/ui/interface/#ui.interface.UI.text","title":"text(msg) abstractmethod","text":"

    Displays a messages that overwrites previous messages if they were created during an interactive session. If not supported or not on an interactive session, it is expected to fallback to the UI print function.

    Parameters:

    Name Type Description Default msg str

    message to display

    required Source code in cli/medperf/ui/interface.py
    @abstractmethod\ndef text(self, msg: str):\n\"\"\"Displays a messages that overwrites previous messages if they were created\n    during an interactive session.\n    If not supported or not on an interactive session, it is expected to fallback\n    to the UI print function.\n    Args:\n        msg (str): message to display\n    \"\"\"\n
    "},{"location":"reference/ui/stdin/","title":"Stdin","text":""},{"location":"reference/ui/stdin/#ui.stdin.StdIn","title":"StdIn","text":"

    Bases: UI

    Class for using sys.stdin/sys.stdout exclusively. Used mainly for automating execution with class-like objects. Using only basic IO methods ensures that piping from the command-line. Should not be used in normal execution, as hidden prompts and interactive prints will not work as expected.

    Source code in cli/medperf/ui/stdin.py
    class StdIn(UI):\n\"\"\"\n    Class for using sys.stdin/sys.stdout exclusively. Used mainly for automating\n    execution with class-like objects. Using only basic IO methods ensures that\n    piping from the command-line. Should not be used in normal execution, as\n    hidden prompts and interactive prints will not work as expected.\n    \"\"\"\ndef print(self, msg: str = \"\"):\nreturn print(msg)\ndef print_error(self, msg: str):\nreturn self.print(msg)\ndef start_interactive(self):\npass\ndef stop_interactive(self):\npass\n@contextmanager\ndef interactive(self):\nyield self\n@property\ndef text(self):\nreturn \"\"\n@text.setter\ndef text(self, msg: str = \"\"):\nreturn\ndef prompt(self, msg: str) -> str:\nreturn input(msg)\ndef hidden_prompt(self, msg: str) -> str:\nreturn self.prompt(msg)\n
    "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..be1c48540 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,573 @@ + + + + https://medperf.com/ + 2024-04-05 + daily + + + https://medperf.com/cli_reference/ + 2024-04-05 + daily + + + https://medperf.com/medperf_components/ + 2024-04-05 + daily + + + https://medperf.com/roles/ + 2024-04-05 + daily + + + https://medperf.com/what_is_medperf/ + 2024-04-05 + daily + + + https://medperf.com/workflow/ + 2024-04-05 + daily + + + https://medperf.com/concepts/associations/ + 2024-04-05 + daily + + + https://medperf.com/concepts/auth/ + 2024-04-05 + daily + + + https://medperf.com/concepts/hosting_files/ + 2024-04-05 + daily + + + https://medperf.com/concepts/mlcube_files/ + 2024-04-05 + daily + + + https://medperf.com/concepts/priorities/ + 2024-04-05 + daily + + + https://medperf.com/concepts/profiles/ + 2024-04-05 + daily + + + https://medperf.com/concepts/single_run/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/benchmark_owner_demo/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/data_owner_demo/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/installation/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/model_owner_demo/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/overview/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/setup/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/signup/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/tutorials_overview/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/shared/before_we_start/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/shared/cleanup/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/shared/mlcube_submission_overview/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/shared/redirect_to_hosting_files/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/shared/tutorials_content_overview/benchmark/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/shared/tutorials_content_overview/data/ + 2024-04-05 + daily + + + https://medperf.com/getting_started/shared/tutorials_content_overview/model/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/gandlf_mlcube/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/mlcube_data/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/mlcube_data_WIP/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/mlcube_metrics/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/mlcube_metrics_WIP/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/mlcube_models/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/mlcubes/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/build/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/contents/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/cookiecutter/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/docker_file/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/execute/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/hello_world/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/hotfix/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/requirements/ + 2024-04-05 + daily + + + https://medperf.com/mlcubes/shared/setup/ + 2024-04-05 + daily + + + https://medperf.com/reference/SUMMARY/ + 2024-04-05 + daily + + + https://medperf.com/reference/_version/ + 2024-04-05 + daily + + + https://medperf.com/reference/cli/ + 2024-04-05 + daily + + + https://medperf.com/reference/config/ + 2024-04-05 + daily + + + https://medperf.com/reference/decorators/ + 2024-04-05 + daily + + + https://medperf.com/reference/enums/ + 2024-04-05 + daily + + + https://medperf.com/reference/exceptions/ + 2024-04-05 + daily + + + https://medperf.com/reference/init/ + 2024-04-05 + daily + + + https://medperf.com/reference/utils/ + 2024-04-05 + daily + + + https://medperf.com/reference/account_management/account_management/ + 2024-04-05 + daily + + + https://medperf.com/reference/account_management/token_storage/filesystem/ + 2024-04-05 + daily + + + https://medperf.com/reference/account_management/token_storage/keyring_/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/execution/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/list/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/profile/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/storage/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/view/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/association/approval/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/association/association/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/association/list/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/association/priority/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/auth/auth/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/auth/login/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/auth/logout/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/auth/status/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/auth/synapse_login/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/benchmark/associate/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/benchmark/benchmark/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/benchmark/submit/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/compatibility_test/compatibility_test/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/compatibility_test/run/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/compatibility_test/utils/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/compatibility_test/validate_params/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/dataset/associate/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/dataset/dataset/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/dataset/prepare/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/dataset/set_operational/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/dataset/submit/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/mlcube/associate/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/mlcube/create/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/mlcube/mlcube/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/mlcube/submit/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/result/create/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/result/result/ + 2024-04-05 + daily + + + https://medperf.com/reference/commands/result/submit/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/factory/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/interface/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/rest/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/auth/auth0/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/auth/interface/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/auth/local/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/auth/token_verifier/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/entity_resources/resources/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/entity_resources/utils/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/entity_resources/sources/direct/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/entity_resources/sources/source/ + 2024-04-05 + daily + + + https://medperf.com/reference/comms/entity_resources/sources/synapse/ + 2024-04-05 + daily + + + https://medperf.com/reference/config_management/config_management/ + 2024-04-05 + daily + + + https://medperf.com/reference/entities/benchmark/ + 2024-04-05 + daily + + + https://medperf.com/reference/entities/cube/ + 2024-04-05 + daily + + + https://medperf.com/reference/entities/dataset/ + 2024-04-05 + daily + + + https://medperf.com/reference/entities/interface/ + 2024-04-05 + daily + + + https://medperf.com/reference/entities/report/ + 2024-04-05 + daily + + + https://medperf.com/reference/entities/result/ + 2024-04-05 + daily + + + https://medperf.com/reference/entities/schemas/ + 2024-04-05 + daily + + + https://medperf.com/reference/storage/utils/ + 2024-04-05 + daily + + + https://medperf.com/reference/ui/cli/ + 2024-04-05 + daily + + + https://medperf.com/reference/ui/factory/ + 2024-04-05 + daily + + + https://medperf.com/reference/ui/interface/ + 2024-04-05 + daily + + + https://medperf.com/reference/ui/stdin/ + 2024-04-05 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 000000000..7f3dd4507 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/snippets/model_mlcube/infer_unorganized.py b/snippets/model_mlcube/infer_unorganized.py new file mode 100644 index 000000000..0f6c7d132 --- /dev/null +++ b/snippets/model_mlcube/infer_unorganized.py @@ -0,0 +1,35 @@ +import torch +from models import SimpleCNN +from tqdm import tqdm +from torch.utils.data import DataLoader +from data_loader import CustomImageDataset +from pprint import pprint + + +data_path = "path/to/data/folder" +weights = "path/to/weights.pt" +in_channels = 1 +num_classes = 14 +batch_size = 5 + +# load model +model = SimpleCNN(in_channels=in_channels, num_classes=num_classes) +model.load_state_dict(torch.load(weights)) +model.eval() + +# load prepared data +dataset = CustomImageDataset(data_path) +dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False) + +# inference +predictions_dict = {} +with torch.no_grad(): + for images, files_ids in tqdm(dataloader): + outputs = model(images) + outputs = torch.nn.Sigmoid()(outputs) + outputs = outputs.detach().numpy() + + for file_id, output in zip(files_ids, outputs): + predictions_dict[file_id] = output + +pprint(predictions_dict) diff --git a/snippets/model_mlcube/mlcube.py b/snippets/model_mlcube/mlcube.py new file mode 100644 index 000000000..32fdb987b --- /dev/null +++ b/snippets/model_mlcube/mlcube.py @@ -0,0 +1,27 @@ +"""MLCube handler file""" +import typer + +app = typer.Typer() + + +@app.command("infer") +def infer( + data_path: str = typer.Option(..., "--data_path"), + parameters_file: str = typer.Option(..., "--parameters_file"), + output_path: str = typer.Option(..., "--output_path"), + # Provide additional parameters as described in the mlcube.yaml file + # e.g. model weights: + # weights: str = typer.Option(..., "--weights"), +): + # Modify the prepare command as needed + raise NotImplementedError("The evaluate method is not yet implemented") + + +@app.command("hotfix") +def hotfix(): + # NOOP command for typer to behave correctly. DO NOT REMOVE OR MODIFY + pass + + +if __name__ == "__main__": + app() diff --git a/snippets/model_mlcube/mlcube.yaml b/snippets/model_mlcube/mlcube.yaml new file mode 100644 index 000000000..f702e90a2 --- /dev/null +++ b/snippets/model_mlcube/mlcube.yaml @@ -0,0 +1,29 @@ +name: Model MLCube +description: Model MLCube Template. Provided by MLCommons +authors: + - { name: John Smith } + +platform: + accelerator_count: 0 + +docker: + # Image name + image: docker/image:latest + # Docker build context relative to $MLCUBE_ROOT. Default is `build`. + build_context: "../project" + # Docker file name within docker build context, default is `Dockerfile`. + build_file: "Dockerfile" + +tasks: + infer: + # Computes predictions on input data + parameters: + inputs: { + data_path: data/, + parameters_file: parameters.yaml, + # Feel free to include other files required for inference. + # These files MUST go inside the additional_files path. + # e.g. model weights + # weights: additional_files/weights.pt, + } + outputs: { output_path: { type: directory, default: predictions } } diff --git a/styles/styles.css b/styles/styles.css new file mode 100644 index 000000000..d754b62a1 --- /dev/null +++ b/styles/styles.css @@ -0,0 +1,183 @@ +:root, [data-md-color-scheme=default] { + --md-card-bg-color: rgb(255, 255, 255); + --md-card-box-shadow: 0 10px 20px #282a3017; + --md-card-border-color: rgba(0, 0, 0, 0.147); + --md-white-color: rgb(255, 255, 255); +} + +:root, [data-md-color-scheme=slate] { + --md-card-bg-color: rgba(90, 90, 90, 0.512); + --md-card-box-shadow: 0 10px 20px rgba(210, 210, 210, 0.315); + --md-card-border-color: rgba(83, 83, 83, 0.581); + --md-white-color: rgb(255, 255, 255); +} + +.side-container { + min-width: 10rem; + max-width: 40rem; + align-self: flex-start; + flex-shrink: 0; + width: 35%; + position: sticky; + top: 100px; + background-color: transparent; + z-index: 0 !important; +} + +@media screen and (max-width: 70em) { + .side-container { + display: none; + } +} + +.tutorial-sticky-image { + width: 100%; + height: 100%; + z-index: 0 !important; +} + +.tutorial-sticky-image-content { + width: 0; + height: 0; +} + +.md-main__inner.md-grid { + max-width: 75rem; +} + + +.md-header__inner { + max-width: 70rem !important; +} + +.md-nav__title { + text-transform: uppercase; + margin-left: 0 !important; +} + +.md-nav__link { + margin-left: 15px; +} + +#__nav_6_label, #__nav_5_label { + margin-left: 0 !important; +} + +.md-nav__item--section {/* md-nav__item--nested */ + margin-top: 40px; +} + +.tutorial_card h2 { + margin-top: 0; + margin-bottom: 10px; + font-weight: 500; +} + +.tutorial_card p { + margin: 0; +} + +.md-header__ellipsis { + justify-content: space-around; +} + +.header_home_btn { + background-color: transparent; + top: 10px; + margin: 0; + margin-left: 215px; + font-size: 16px; +} + +.md-header__topic { + font-weight: 700; +} + +.tutorial_card_container { + justify-content: center; + align-items: center; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 20px; +} + +.tutorial_card_container .card { + background-color: var(--md-card-bg-color); + border: 1px solid var(--md-card-border-color); + border-radius: 6px; + display: flex; + flex-direction: column; + padding: 12px; + transition: all 0.3s ease, border 0.1s ease; + flex: 1; + align-self: stretch; + color: var(--md-typeset-color); +} + +.tutorial_card_container .card:hover { + border: 1px solid transparent; + box-shadow: var(--md-card-box-shadow); + text-decoration: none; + color: inherit !important; + transform: translateY(-2px); +} + +.tutorial_card_container .card .card_image { + background-position: center; + background-size: cover; + border-radius: 6px; + height: 80px; + padding: 10px; +} + +.tutorial_card_container .card .image1 { + background-image: url('https://raw.githubusercontent.com/gabrielraeder/website/main/docs/static/images/cards/card1_img.png'); +} + +.tutorial_card_container .card .image2 { + background-image: url('https://raw.githubusercontent.com/gabrielraeder/website/main/docs/static/images/cards/card2_img.png'); +} + +.tutorial_card_container .card .image3 { + background-image: url('https://raw.githubusercontent.com/gabrielraeder/website/main/docs/static/images/cards/card3_img.png'); +} + +.tutorial_card_container .card_text { + padding-top: 12px; + margin-left: 1px; +} + +.tutorial_card_container .card_title { + margin: 5px 0; + font-weight: bold; + font-size: 16.5px; +} + +.tutorial_card_container .card_text p { + margin: 8px 0 0 0; + min-height: 40px; + height: fit-content; +} + +.tutorial_card_container .card a { + border-radius: 15px; + font-weight: 700; + margin-top: 8px; + padding: 3px 0; + text-decoration: none; + transition: color 0.3s ease; + width: fit-content; +} + +.tutorial_card_container .card:hover a { + text-decoration: underline !important; + color: inherit !important; +} + +.tutorial_card_container .card p { + font-size: 14px; + line-height: 1.8em; + -webkit-font-smoothing: antialiased; + margin: 0 0 10px; + display: block; +} \ No newline at end of file diff --git a/tutorial_images/bc-1-bc-implements-mlcubes.png b/tutorial_images/bc-1-bc-implements-mlcubes.png new file mode 100644 index 000000000..026e2b511 Binary files /dev/null and b/tutorial_images/bc-1-bc-implements-mlcubes.png differ diff --git a/tutorial_images/bc-2-bc-develops-dataset.png b/tutorial_images/bc-2-bc-develops-dataset.png new file mode 100644 index 000000000..d4b514c5a Binary files /dev/null and b/tutorial_images/bc-2-bc-develops-dataset.png differ diff --git a/tutorial_images/bc-3-bc-submits-mlcubes.png b/tutorial_images/bc-3-bc-submits-mlcubes.png new file mode 100644 index 000000000..50ad3485a Binary files /dev/null and b/tutorial_images/bc-3-bc-submits-mlcubes.png differ diff --git a/tutorial_images/bc-4-bc-submits-data-preparator.png b/tutorial_images/bc-4-bc-submits-data-preparator.png new file mode 100644 index 000000000..d22f3be97 Binary files /dev/null and b/tutorial_images/bc-4-bc-submits-data-preparator.png differ diff --git a/tutorial_images/bc-5-bc-submits-ref-model.png b/tutorial_images/bc-5-bc-submits-ref-model.png new file mode 100644 index 000000000..9890e82e3 Binary files /dev/null and b/tutorial_images/bc-5-bc-submits-ref-model.png differ diff --git a/tutorial_images/bc-6-bc-submits-evalmetrics.png b/tutorial_images/bc-6-bc-submits-evalmetrics.png new file mode 100644 index 000000000..ae1f88b21 Binary files /dev/null and b/tutorial_images/bc-6-bc-submits-evalmetrics.png differ diff --git a/tutorial_images/bc-7-bc-submits-benchmark.png b/tutorial_images/bc-7-bc-submits-benchmark.png new file mode 100644 index 000000000..6823d9396 Binary files /dev/null and b/tutorial_images/bc-7-bc-submits-benchmark.png differ diff --git a/tutorial_images/do-1-do-registers-dataset.png b/tutorial_images/do-1-do-registers-dataset.png new file mode 100644 index 000000000..a6871186f Binary files /dev/null and b/tutorial_images/do-1-do-registers-dataset.png differ diff --git a/tutorial_images/do-2-do-prepares-datset.png b/tutorial_images/do-2-do-prepares-datset.png new file mode 100644 index 000000000..bc20081fd Binary files /dev/null and b/tutorial_images/do-2-do-prepares-datset.png differ diff --git a/tutorial_images/do-2.5-do-marks-dataset-operational.png b/tutorial_images/do-2.5-do-marks-dataset-operational.png new file mode 100644 index 000000000..7616fa112 Binary files /dev/null and b/tutorial_images/do-2.5-do-marks-dataset-operational.png differ diff --git a/tutorial_images/do-3-do-requests-participation.png b/tutorial_images/do-3-do-requests-participation.png new file mode 100644 index 000000000..108bfec64 Binary files /dev/null and b/tutorial_images/do-3-do-requests-participation.png differ diff --git a/tutorial_images/do-4-bc-accepts-rejects-datasets.png b/tutorial_images/do-4-bc-accepts-rejects-datasets.png new file mode 100644 index 000000000..ae49b9f86 Binary files /dev/null and b/tutorial_images/do-4-bc-accepts-rejects-datasets.png differ diff --git a/tutorial_images/do-5-do-runs-models.png b/tutorial_images/do-5-do-runs-models.png new file mode 100644 index 000000000..ecf87bda8 Binary files /dev/null and b/tutorial_images/do-5-do-runs-models.png differ diff --git a/tutorial_images/do-6-do-submits-eval-results.png b/tutorial_images/do-6-do-submits-eval-results.png new file mode 100644 index 000000000..7ed77e1ec Binary files /dev/null and b/tutorial_images/do-6-do-submits-eval-results.png differ diff --git a/tutorial_images/do-7-bc-reads-results.png b/tutorial_images/do-7-bc-reads-results.png new file mode 100644 index 000000000..d0b876ba6 Binary files /dev/null and b/tutorial_images/do-7-bc-reads-results.png differ diff --git a/tutorial_images/mo-1-mo-implements-cube.png b/tutorial_images/mo-1-mo-implements-cube.png new file mode 100644 index 000000000..436be7251 Binary files /dev/null and b/tutorial_images/mo-1-mo-implements-cube.png differ diff --git a/tutorial_images/mo-2-mo-submits-model.png b/tutorial_images/mo-2-mo-submits-model.png new file mode 100644 index 000000000..d7de7a294 Binary files /dev/null and b/tutorial_images/mo-2-mo-submits-model.png differ diff --git a/tutorial_images/mo-3-mo-requests-participation.png b/tutorial_images/mo-3-mo-requests-participation.png new file mode 100644 index 000000000..6ff264000 Binary files /dev/null and b/tutorial_images/mo-3-mo-requests-participation.png differ diff --git a/tutorial_images/mo-4-bc-accepts-rejects-models.png b/tutorial_images/mo-4-bc-accepts-rejects-models.png new file mode 100644 index 000000000..00825179e Binary files /dev/null and b/tutorial_images/mo-4-bc-accepts-rejects-models.png differ diff --git a/tutorial_images/overview.png b/tutorial_images/overview.png new file mode 100644 index 000000000..f466144c2 Binary files /dev/null and b/tutorial_images/overview.png differ diff --git a/tutorial_images/the-end.png b/tutorial_images/the-end.png new file mode 100644 index 000000000..d91c64a50 Binary files /dev/null and b/tutorial_images/the-end.png differ diff --git a/what_is_medperf/index.html b/what_is_medperf/index.html new file mode 100644 index 000000000..077dca9ae --- /dev/null +++ b/what_is_medperf/index.html @@ -0,0 +1,1002 @@ + + + + + + + + + + + + + + + + + + + + + + What is Medperf? - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + + +
    + + + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    + + + + + + + +

    What is Medperf?

    + + +

    MedPerf is an open-source framework for benchmarking medical ML models. It uses Federated Evaluation a method in which medical ML models are securely distributed to multiple global facilities for evaluation prioritizing patient privacy to mitigate legal and regulatory risks. The goal of Federated Evaluation is to make it simple and reliable to share ML models with many data providers, evaluate those ML models against their data in controlled settings, then aggregate and analyze the findings.

    +

    The MedPerf approach empowers healthcare stakeholders through neutral governance to assess and verify the performance of ML models in an efficient and human-supervised process without sharing any patient data across facilities during the process.

    + + + + + + + + + + + +
    federated_evaluation.gif
    Federated evaluation of medical AI model using MedPerf on a hypothetical example
    +

    Why MedPerf?

    +

    MedPerf aims to identify bias and generalizability issues of medical ML models by evaluating them on diverse medical data across the world. This process allows developers of medical ML to efficiently identify performance and reliability issues on their models while healthcare stakeholders (e.g., hospitals, practices, etc.) can validate such models against clinical efficacy.

    +

    Importantly, MedPerf supports technology for neutral governance in order to enable full trust and transparency among participating parties (e.g., AI vendor, data provider, regulatory body, etc.). This is all encapsulated in the benchmark committee which is the overseeing body on a benchmark.

    + + + + + + + + + + + +
    benchmark_committee.gif
    Benchmark committee in MedPerf
    +

    Benefits to healthcare stakeholders

    +

    Anyone who joins our platform can get several benefits, regardless of the role they will assume.

    + + + + + + + + + + + +
    benefits.png
    Benefits to healthacare stakeholders using MedPerf
    +

    Our paper describes the design philosophy in detail.

    + + + + + + + + + + + +
    +
    + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + \ No newline at end of file diff --git a/workflow/index.html b/workflow/index.html new file mode 100644 index 000000000..8348cbd9a --- /dev/null +++ b/workflow/index.html @@ -0,0 +1,1050 @@ + + + + + + + + + + + + + + + + + + + + + + + + Benchmark Workflow - MedPerf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + + + + +
    + + + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + +
    +
    +
    + + + + + + + +
    +
    + + + + + + + +

    Benchmark Workflow

    + + + + + + + +

    A benchmark in MedPerf is a collection of assets that are developed by the benchmark committee that aims to evaluate medical ML on decentralized data providers.

    +

    The process is simple yet effective enabling scalability.

    +

    Step 1. Establish Benchmark Committee

    +

    The benchmarking process starts with establishing a benchmark committee of healthcare stakeholders (experts, committee), which will identify a clinical problem where an effective ML-based solution can have a significant clinical impact.

    + + +

    Step 2. Register Benchmark

    +

    MLCubes are the building blocks of an experiment and are required in order to create a benchmark. Three MLCubes (Data Preparator MLCube, Reference Model MLCube, and Metrics MLCube) need to be submitted. After submitting the three MLCubes, alongside with a sample reference dataset, the Benchmark Committee is capable of creating a benchmark. Once the benchmark is submitted, the Medperf admin must approve it before it can be seen by other users. Follow our Hands-on Tutorial for detailed step-by-step guidelines.

    +

    Step 3. Register Dataset

    +

    Data Providers that want to be part of the benchmark can register their own datasets, prepare them, and associate them with the benchmark. A dataset will be prepared using the benchmark's Data Preparator MLCube and the dataset's metadata is registered within the MedPerf server.

    + + + + + + + + + + + +
    flow_preparation.gif
    Data Preparation
    +

    The data provider then can request to participate in the benchmark with their dataset. Requesting the association will run the benchmark's reference workflow to assure the compatibility of the prepared dataset structure with the workflow. Once the association request is approved by the Benchmark Committee, then the dataset becomes a part of the benchmark.

    +

    +

    Step 4. Register Models

    +

    Once a benchmark is submitted by the Benchmark Committee, any user can submit their own Model MLCubes and request an association with the benchmark. This association request executes the benchmark locally with the given model on the benchmark's reference dataset to ensure workflow validity and compatibility. If the model successfully passes the compatibility test, and its association is approved by the Benchmark Committee, it becomes a part of the benchmark.

    +

    +

    Step 5. Execute Benchmark

    +

    The Benchmark Committee may notify Data Providers that models are available for benchmarking. Data Providers can then run the benchmark models locally on their data.

    +

    This procedure retrieves the model MLCubes associated with the benchmark and runs them on the indicated prepared dataset to generate predictions. The Metrics MLCube of the benchmark is then retrieved to evaluate the predictions. Once the evaluation results are generated, the data provider can submit them to the platform.

    +

    +

    Step 6. Aggregate and Release Results

    +

    The benchmarking platform aggregates the results of running the models against the datasets and shares them according to the Benchmark Committee's policy.

    +

    The sharing policy controls how much of the data is shared, ranging from a single aggregated metric to a more detailed model-data cross product. A public leaderboard is available to Model Owners who produce the best performances.

    + + + + + + + + + + +
    +
    + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + \ No newline at end of file