Spotsweeper-py: spatially-aware quality control... | F1000Research "use strict";function _typeof(t){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}!function(){var t=function(){var t,e,o=[],n=window,r=n;for(;r;){try{if(r.frames.__tcfapiLocator){t=r;break}}catch(t){}if(r===n.top)break;r=r.parent}t||(!function t(){var e=n.document,o=!!n.frames.__tcfapiLocator;if(!o)if(e.body){var r=e.createElement("iframe");r.style.cssText="display:none",r.name="__tcfapiLocator",e.body.appendChild(r)}else setTimeout(t,5);return!o}(),n.__tcfapi=function(){for(var t=arguments.length,n=new Array(t),r=0;r 3&&2===parseInt(n[1],10)&&"boolean"==typeof n[3]&&(e=n[3],"function"==typeof n[2]&&n[2]("set",!0)):"ping"===n[0]?"function"==typeof n[2]&&n[2]({gdprApplies:e,cmpLoaded:!1,cmpStatus:"stub"}):o.push(n)},n.addEventListener("message",(function(t){var e="string"==typeof t.data,o={};if(e)try{o=JSON.parse(t.data)}catch(t){}else o=t.data;var n="object"===_typeof(o)&&null!==o?o.__tcfapiCall:null;n&&window.__tcfapi(n.command,n.version,(function(o,r){var a={__tcfapiReturn:{returnValue:o,success:r,callId:n.callId}};t&&t.source&&t.source.postMessage&&t.source.postMessage(e?JSON.stringify(a):a,"*")}),n.parameter)}),!1))};"undefined"!=typeof module?module.exports=t:t()}(); dataLayer = dataLayer || []; // Standard GTM initialization - Google Consent Mode handles consent automatically (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl+ '>m_auth=hzk0Vc3qFsQYhCrIoHz68A>m_preview=env-1>m_cookies_win=x';f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-MWFK8L5J'); ;window.NREUM||(NREUM={});NREUM.init={distributed_tracing:{enabled:true},privacy:{cookies_enabled:true},ajax:{deny_list:["bam.nr-data.net"]}}; ;NREUM.loader_config={accountID:"438030",trustKey:"438030",agentID:"772317073",licenseKey:"97f8f67f26",applicationID:"772317073"} ;NREUM.info={beacon:"bam.nr-data.net",errorBeacon:"bam.nr-data.net",licenseKey:"97f8f67f26",applicationID:"772317073",sa:1} ;/*! For license information please see nr-loader-spa-1.236.0.min.js.LICENSE.txt */ (()=>{"use strict";var e,t,r={5763:(e,t,r)=>{r.d(t,{P_:()=>l,Mt:()=>g,C5:()=>s,DL:()=>v,OP:()=>T,lF:()=>D,Yu:()=>y,Dg:()=>h,CX:()=>c,GE:()=>b,sU:()=>_});var n=r(8632),i=r(9567);const o={beacon:n.ce.beacon,errorBeacon:n.ce.errorBeacon,licenseKey:void 0,applicationID:void 0,sa:void 0,queueTime:void 0,applicationTime:void 0,ttGuid:void 0,user:void 0,account:void 0,product:void 0,extra:void 0,jsAttributes:{},userAttributes:void 0,atts:void 0,transactionName:void 0,tNamePlain:void 0},a={};function s(e){if(!e)throw new Error("All info objects require an agent identifier!");if(!a[e])throw new Error("Info for ".concat(e," was never set"));return a[e]}function c(e,t){if(!e)throw new Error("All info objects require an agent identifier!");a[e]=(0,i.D)(t,o),(0,n.Qy)(e,a[e],"info")}var u=r(7056);const d=()=>{const e={blockSelector:"[data-nr-block]",maskInputOptions:{password:!0}};return{allow_bfcache:!0,privacy:{cookies_enabled:!0},ajax:{deny_list:void 0,enabled:!0,harvestTimeSeconds:10},distributed_tracing:{enabled:void 0,exclude_newrelic_header:void 0,cors_use_newrelic_header:void 0,cors_use_tracecontext_headers:void 0,allowed_origins:void 0},session:{domain:void 0,expiresMs:u.oD,inactiveMs:u.Hb},ssl:void 0,obfuscate:void 0,jserrors:{enabled:!0,harvestTimeSeconds:10},metrics:{enabled:!0},page_action:{enabled:!0,harvestTimeSeconds:30},page_view_event:{enabled:!0},page_view_timing:{enabled:!0,harvestTimeSeconds:30,long_task:!1},session_trace:{enabled:!0,harvestTimeSeconds:10},harvest:{tooManyRequestsDelay:60},session_replay:{enabled:!1,harvestTimeSeconds:60,sampleRate:.1,errorSampleRate:.1,maskTextSelector:"*",maskAllInputs:!0,get blockClass(){return"nr-block"},get ignoreClass(){return"nr-ignore"},get maskTextClass(){return"nr-mask"},get blockSelector(){return e.blockSelector},set blockSelector(t){e.blockSelector+=",".concat(t)},get maskInputOptions(){return e.maskInputOptions},set maskInputOptions(t){e.maskInputOptions={...t,password:!0}}},spa:{enabled:!0,harvestTimeSeconds:10}}},f={};function l(e){if(!e)throw new Error("All configuration objects require an agent identifier!");if(!f[e])throw new Error("Configuration for ".concat(e," was never set"));return f[e]}function h(e,t){if(!e)throw new Error("All configuration objects require an agent identifier!");f[e]=(0,i.D)(t,d()),(0,n.Qy)(e,f[e],"config")}function g(e,t){if(!e)throw new Error("All configuration objects require an agent identifier!");var r=l(e);if(r){for(var n=t.split("."),i=0;i {r.d(t,{D:()=>i});var n=r(50);function i(e,t){try{if(!e||"object"!=typeof e)return(0,n.Z)("Setting a Configurable requires an object as input");if(!t||"object"!=typeof t)return(0,n.Z)("Setting a Configurable requires a model to set its initial properties");const r=Object.create(Object.getPrototypeOf(t),Object.getOwnPropertyDescriptors(t)),o=0===Object.keys(r).length?e:r;for(let a in o)if(void 0!==e[a])try{"object"==typeof e[a]&&"object"==typeof t[a]?r[a]=i(e[a],t[a]):r[a]=e[a]}catch(e){(0,n.Z)("An error occurred while setting a property of a Configurable",e)}return r}catch(e){(0,n.Z)("An error occured while setting a Configurable",e)}}},6818:(e,t,r)=>{r.d(t,{Re:()=>i,gF:()=>o,q4:()=>n});const n="1.236.0",i="PROD",o="CDN"},385:(e,t,r)=>{r.d(t,{FN:()=>a,IF:()=>u,Nk:()=>f,Tt:()=>s,_A:()=>o,il:()=>n,pL:()=>c,v6:()=>i,w1:()=>d});const n="undefined"!=typeof window&&!!window.document,i="undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self.navigator instanceof WorkerNavigator||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis.navigator instanceof WorkerNavigator),o=n?window:"undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis),a=""+o?.location,s=/iPad|iPhone|iPod/.test(navigator.userAgent),c=s&&"undefined"==typeof SharedWorker,u=(()=>{const e=navigator.userAgent.match(/Firefox[/\s](\d+\.\d+)/);return Array.isArray(e)&&e.length>=2?+e[1]:0})(),d=Boolean(n&&window.document.documentMode),f=!!navigator.sendBeacon},1117:(e,t,r)=>{r.d(t,{w:()=>o});var n=r(50);const i={agentIdentifier:"",ee:void 0};class o{constructor(e){try{if("object"!=typeof e)return(0,n.Z)("shared context requires an object as input");this.sharedContext={},Object.assign(this.sharedContext,i),Object.entries(e).forEach((e=>{let[t,r]=e;Object.keys(i).includes(t)&&(this.sharedContext[t]=r)}))}catch(e){(0,n.Z)("An error occured while setting SharedContext",e)}}}},8e3:(e,t,r)=>{r.d(t,{L:()=>d,R:()=>c});var n=r(2177),i=r(1284),o=r(4322),a=r(3325);const s={};function c(e,t){const r={staged:!1,priority:a.p[t]||0};u(e),s[e].get(t)||s[e].set(t,r)}function u(e){e&&(s[e]||(s[e]=new Map))}function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"feature";if(u(e),!e||!s[e].get(t))return a(t);s[e].get(t).staged=!0;const r=[...s[e]];function a(t){const r=e?n.ee.get(e):n.ee,a=o.X.handlers;if(r.backlog&&a){var s=r.backlog[t],c=a[t];if(c){for(var u=0;s&&u {let[t,r]=e;return r.staged}))&&(r.sort(((e,t)=>e[1].priority-t[1].priority)),r.forEach((e=>{let[t]=e;a(t)})))}function f(e,t){var r=e[1];(0,i.D)(t[r],(function(t,r){var n=e[0];if(r[0]===n){var i=r[1],o=e[3],a=e[2];i.apply(o,a)}}))}},2177:(e,t,r)=>{r.d(t,{c:()=>f,ee:()=>u});var n=r(8632),i=r(2210),o=r(1284),a=r(5763),s="nr@context";let c=(0,n.fP)();var u;function d(){}function f(e){return(0,i.X)(e,s,l)}function l(){return new d}function h(){u.aborted=!0,u.backlog={}}c.ee?u=c.ee:(u=function e(t,r){var n={},c={},f={},g=!1;try{g=16===r.length&&(0,a.OP)(r).isolatedBacklog}catch(e){}var p={on:b,addEventListener:b,removeEventListener:y,emit:v,get:x,listeners:w,context:m,buffer:A,abort:h,aborted:!1,isBuffering:E,debugId:r,backlog:g?{}:t&&"object"==typeof t.backlog?t.backlog:{}};return p;function m(e){return e&&e instanceof d?e:e?(0,i.X)(e,s,l):l()}function v(e,r,n,i,o){if(!1!==o&&(o=!0),!u.aborted||i){t&&o&&t.emit(e,r,n);for(var a=m(n),s=w(e),d=s.length,f=0;fn,p:()=>i});var n=r(2177).ee.get("handle");function i(e,t,r,i,o){o?(o.buffer([e],i),o.emit(e,t,r)):(n.buffer([e],i),n.emit(e,t,r))}},4322:(e,t,r)=>{r.d(t,{X:()=>o});var n=r(5546);o.on=a;var i=o.handlers={};function o(e,t,r,o){a(o||n.E,i,e,t,r)}function a(e,t,r,i,o){o||(o="feature"),e||(e=n.E);var a=t[o]=t[o]||{};(a[r]=a[r]||[]).push([e,i])}},3239:(e,t,r)=>{r.d(t,{bP:()=>s,iz:()=>c,m$:()=>a});var n=r(385);let i=!1,o=!1;try{const e={get passive(){return i=!0,!1},get signal(){return o=!0,!1}};n._A.addEventListener("test",null,e),n._A.removeEventListener("test",null,e)}catch(e){}function a(e,t){return i||o?{capture:!!e,passive:i,signal:t}:!!e}function s(e,t){let r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=arguments.length>3?arguments[3]:void 0;window.addEventListener(e,t,a(r,n))}function c(e,t){let r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=arguments.length>3?arguments[3]:void 0;document.addEventListener(e,t,a(r,n))}},4402:(e,t,r)=>{r.d(t,{Ht:()=>u,M:()=>c,Rl:()=>a,ky:()=>s});var n=r(385);const i="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";function o(e,t){return e?15&e[t]:16*Math.random()|0}function a(){const e=n._A?.crypto||n._A?.msCrypto;let t,r=0;return e&&e.getRandomValues&&(t=e.getRandomValues(new Uint8Array(31))),i.split("").map((e=>"x"===e?o(t,++r).toString(16):"y"===e?(3&o()|8).toString(16):e)).join("")}function s(e){const t=n._A?.crypto||n._A?.msCrypto;let r,i=0;t&&t.getRandomValues&&(r=t.getRandomValues(new Uint8Array(31)));const a=[];for(var s=0;s {r.d(t,{Bq:()=>n,Hb:()=>o,oD:()=>i});const n="NRBA",i=144e5,o=18e5},7894:(e,t,r)=>{function n(){return Math.round(performance.now())}r.d(t,{z:()=>n})},7243:(e,t,r)=>{r.d(t,{e:()=>o});var n=r(385),i={};function o(e){if(e in i)return i[e];if(0===(e||"").indexOf("data:"))return{protocol:"data"};let t;var r=n._A?.location,o={};if(n.il)t=document.createElement("a"),t.href=e;else try{t=new URL(e,r.href)}catch(e){return o}o.port=t.port;var a=t.href.split("://");!o.port&&a[1]&&(o.port=a[1].split("/")[0].split("@").pop().split(":")[1]),o.port&&"0"!==o.port||(o.port="https"===a[0]?"443":"80"),o.hostname=t.hostname||r.hostname,o.pathname=t.pathname,o.protocol=a[0],"/"!==o.pathname.charAt(0)&&(o.pathname="/"+o.pathname);var s=!t.protocol||":"===t.protocol||t.protocol===r.protocol,c=t.hostname===r.hostname&&t.port===r.port;return o.sameOrigin=s&&(!t.hostname||c),"/"===o.pathname&&(i[e]=o),o}},50:(e,t,r)=>{function n(e,t){"function"==typeof console.warn&&(console.warn("New Relic: ".concat(e)),t&&console.warn(t))}r.d(t,{Z:()=>n})},2587:(e,t,r)=>{r.d(t,{N:()=>c,T:()=>u});var n=r(2177),i=r(5546),o=r(8e3),a=r(3325);const s={stn:[a.D.sessionTrace],err:[a.D.jserrors,a.D.metrics],ins:[a.D.pageAction],spa:[a.D.spa],sr:[a.D.sessionReplay,a.D.sessionTrace]};function c(e,t){const r=n.ee.get(t);e&&"object"==typeof e&&(Object.entries(e).forEach((e=>{let[t,n]=e;void 0===u[t]&&(s[t]?s[t].forEach((e=>{n?(0,i.p)("feat-"+t,[],void 0,e,r):(0,i.p)("block-"+t,[],void 0,e,r),(0,i.p)("rumresp-"+t,[Boolean(n)],void 0,e,r)})):n&&(0,i.p)("feat-"+t,[],void 0,void 0,r),u[t]=Boolean(n))})),Object.keys(s).forEach((e=>{void 0===u[e]&&(s[e]?.forEach((t=>(0,i.p)("rumresp-"+e,[!1],void 0,t,r))),u[e]=!1)})),(0,o.L)(t,a.D.pageViewEvent))}const u={}},2210:(e,t,r)=>{r.d(t,{X:()=>i});var n=Object.prototype.hasOwnProperty;function i(e,t,r){if(n.call(e,t))return e[t];var i=r();if(Object.defineProperty&&Object.keys)try{return Object.defineProperty(e,t,{value:i,writable:!0,enumerable:!1}),i}catch(e){}return e[t]=i,i}},1284:(e,t,r)=>{r.d(t,{D:()=>n});const n=(e,t)=>Object.entries(e||{}).map((e=>{let[r,n]=e;return t(r,n)}))},4351:(e,t,r)=>{r.d(t,{P:()=>o});var n=r(2177);const i=()=>{const e=new WeakSet;return(t,r)=>{if("object"==typeof r&&null!==r){if(e.has(r))return;e.add(r)}return r}};function o(e){try{return JSON.stringify(e,i())}catch(e){try{n.ee.emit("internal-error",[e])}catch(e){}}}},3960:(e,t,r)=>{r.d(t,{K:()=>a,b:()=>o});var n=r(3239);function i(){return"undefined"==typeof document||"complete"===document.readyState}function o(e,t){if(i())return e();(0,n.bP)("load",e,t)}function a(e){if(i())return e();(0,n.iz)("DOMContentLoaded",e)}},8632:(e,t,r)=>{r.d(t,{EZ:()=>u,Qy:()=>c,ce:()=>o,fP:()=>a,gG:()=>d,mF:()=>s});var n=r(7894),i=r(385);const o={beacon:"bam.nr-data.net",errorBeacon:"bam.nr-data.net"};function a(){return i._A.NREUM||(i._A.NREUM={}),void 0===i._A.newrelic&&(i._A.newrelic=i._A.NREUM),i._A.NREUM}function s(){let e=a();return e.o||(e.o={ST:i._A.setTimeout,SI:i._A.setImmediate,CT:i._A.clearTimeout,XHR:i._A.XMLHttpRequest,REQ:i._A.Request,EV:i._A.Event,PR:i._A.Promise,MO:i._A.MutationObserver,FETCH:i._A.fetch}),e}function c(e,t,r){let i=a();const o=i.initializedAgents||{},s=o[e]||{};return Object.keys(s).length||(s.initializedAt={ms:(0,n.z)(),date:new Date}),i.initializedAgents={...o,[e]:{...s,[r]:t}},i}function u(e,t){a()[e]=t}function d(){return function(){let e=a();const t=e.info||{};e.info={beacon:o.beacon,errorBeacon:o.errorBeacon,...t}}(),function(){let e=a();const t=e.init||{};e.init={...t}}(),s(),function(){let e=a();const t=e.loader_config||{};e.loader_config={...t}}(),a()}},7956:(e,t,r)=>{r.d(t,{N:()=>i});var n=r(3239);function i(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=arguments.length>2?arguments[2]:void 0,i=arguments.length>3?arguments[3]:void 0;return void(0,n.iz)("visibilitychange",(function(){if(t)return void("hidden"==document.visibilityState&&e());e(document.visibilityState)}),r,i)}},1214:(e,t,r)=>{r.d(t,{em:()=>v,u5:()=>N,QU:()=>S,_L:()=>I,Gm:()=>L,Lg:()=>M,gy:()=>U,BV:()=>Q,Kf:()=>ee});var n=r(2177);const i="nr@original";var o=Object.prototype.hasOwnProperty,a=!1;function s(e,t){return e||(e=n.ee),r.inPlace=function(e,t,n,i,o){n||(n="");var a,s,c,u="-"===n.charAt(0);for(c=0;c 2?n-2:0),o=2;o {r(A[T],e,w),r(E[T],e,w)})),r(l._A,"fetch",y),t.on(y+"end",(function(e,r){var n=this;if(r){var i=r.headers.get("content-length");null!==i&&(n.rxSize=i),t.emit(y+"done",[null,r],n)}else t.emit(y+"done",[e],n)})),t}const O={},j=["pushState","replaceState"];function S(e){const t=function(e){return(e||n.ee).get("history")}(e);return!l.il||O[t.debugId]++||(O[t.debugId]=1,s(t).inPlace(window.history,j,"-")),t}var P=r(3239);const C={},R=["appendChild","insertBefore","replaceChild"];function I(e){const t=function(e){return(e||n.ee).get("jsonp")}(e);if(!l.il||C[t.debugId])return t;C[t.debugId]=!0;var r=s(t),i=/[?&](?:callback|cb)=([^&#]+)/,o=/(.*)\.([^.]+)/,a=/^(\w+)(\.|$)(.*)$/;function c(e,t){var r=e.match(a),n=r[1],i=r[3];return i?c(i,t[n]):t[n]}return r.inPlace(Node.prototype,R,"dom-"),t.on("dom-start",(function(e){!function(e){if(!e||"string"!=typeof e.nodeName||"script"!==e.nodeName.toLowerCase())return;if("function"!=typeof e.addEventListener)return;var n=(a=e.src,s=a.match(i),s?s[1]:null);var a,s;if(!n)return;var u=function(e){var t=e.match(o);if(t&&t.length>=3)return{key:t[2],parent:c(t[1],window)};return{key:e,parent:window}}(n);if("function"!=typeof u.parent[u.key])return;var d={};function f(){t.emit("jsonp-end",[],d),e.removeEventListener("load",f,(0,P.m$)(!1)),e.removeEventListener("error",l,(0,P.m$)(!1))}function l(){t.emit("jsonp-error",[],d),t.emit("jsonp-end",[],d),e.removeEventListener("load",f,(0,P.m$)(!1)),e.removeEventListener("error",l,(0,P.m$)(!1))}r.inPlace(u.parent,[u.key],"cb-",d),e.addEventListener("load",f,(0,P.m$)(!1)),e.addEventListener("error",l,(0,P.m$)(!1)),t.emit("new-jsonp",[e.src],d)}(e[0])})),t}var k=r(5763);const H={};function L(e){const t=function(e){return(e||n.ee).get("mutation")}(e);if(!l.il||H[t.debugId])return t;H[t.debugId]=!0;var r=s(t),i=k.Yu.MO;return i&&(window.MutationObserver=function(e){return this instanceof i?new i(r(e,"fn-")):i.apply(this,arguments)},MutationObserver.prototype=i.prototype),t}const z={};function M(e){const t=function(e){return(e||n.ee).get("promise")}(e);if(z[t.debugId])return t;z[t.debugId]=!0;var r=n.c,o=s(t),a=k.Yu.PR;return a&&function(){function e(r){var n=t.context(),i=o(r,"executor-",n,null,!1);const s=Reflect.construct(a,[i],e);return t.context(s).getCtx=function(){return n},s}l._A.Promise=e,Object.defineProperty(e,"name",{value:"Promise"}),e.toString=function(){return a.toString()},Object.setPrototypeOf(e,a),["all","race"].forEach((function(r){const n=a[r];e[r]=function(e){let i=!1;[...e||[]].forEach((e=>{this.resolve(e).then(a("all"===r),a(!1))}));const o=n.apply(this,arguments);return o;function a(e){return function(){t.emit("propagate",[null,!i],o,!1,!1),i=i||!e}}}})),["resolve","reject"].forEach((function(r){const n=a[r];e[r]=function(e){const r=n.apply(this,arguments);return e!==r&&t.emit("propagate",[e,!0],r,!1,!1),r}})),e.prototype=a.prototype;const n=a.prototype.then;a.prototype.then=function(){var e=this,i=r(e);i.promise=e;for(var a=arguments.length,s=new Array(a),c=0;c e())),t};function m(e,t){i.inPlace(t,["onreadystatechange"],"fn-",E)}function b(){var e=this,t=r.context(e);e.readyState>3&&!t.resolved&&(t.resolved=!0,r.emit("xhr-resolved",[],e)),i.inPlace(e,f,"fn-",E)}if(function(e,t){for(var r in e)t[r]=e[r]}(o,p),p.prototype=o.prototype,i.inPlace(p.prototype,J,"-xhr-",E),r.on("send-xhr-start",(function(e,t){m(e,t),function(e){h.push(e),a&&(y?y.then(A):u?u(A):(w=-w,x.data=w))}(t)})),r.on("open-xhr-start",m),a){var y=c&&c.resolve();if(!u&&!c){var w=1,x=document.createTextNode(w);new a(A).observe(x,{characterData:!0})}}else t.on("fn-end",(function(e){e[0]&&e[0].type===d||A()}));function A(){for(var e=0;e {r.d(t,{t:()=>n});const n=r(3325).D.ajax},6660:(e,t,r)=>{r.d(t,{A:()=>i,t:()=>n});const n=r(3325).D.jserrors,i="nr@seenError"},3081:(e,t,r)=>{r.d(t,{gF:()=>o,mY:()=>i,t9:()=>n,vz:()=>s,xS:()=>a});const n=r(3325).D.metrics,i="sm",o="cm",a="storeSupportabilityMetrics",s="storeEventMetrics"},4649:(e,t,r)=>{r.d(t,{t:()=>n});const n=r(3325).D.pageAction},7633:(e,t,r)=>{r.d(t,{Dz:()=>i,OJ:()=>a,qw:()=>o,t9:()=>n});const n=r(3325).D.pageViewEvent,i="firstbyte",o="domcontent",a="windowload"},9251:(e,t,r)=>{r.d(t,{t:()=>n});const n=r(3325).D.pageViewTiming},3614:(e,t,r)=>{r.d(t,{BST_RESOURCE:()=>i,END:()=>s,FEATURE_NAME:()=>n,FN_END:()=>u,FN_START:()=>c,PUSH_STATE:()=>d,RESOURCE:()=>o,START:()=>a});const n=r(3325).D.sessionTrace,i="bstResource",o="resource",a="-start",s="-end",c="fn"+a,u="fn"+s,d="pushState"},7836:(e,t,r)=>{r.d(t,{BODY:()=>A,CB_END:()=>E,CB_START:()=>u,END:()=>x,FEATURE_NAME:()=>i,FETCH:()=>_,FETCH_BODY:()=>v,FETCH_DONE:()=>m,FETCH_START:()=>p,FN_END:()=>c,FN_START:()=>s,INTERACTION:()=>l,INTERACTION_API:()=>d,INTERACTION_EVENTS:()=>o,JSONP_END:()=>b,JSONP_NODE:()=>g,JS_TIME:()=>T,MAX_TIMER_BUDGET:()=>a,REMAINING:()=>f,SPA_NODE:()=>h,START:()=>w,originalSetTimeout:()=>y});var n=r(5763);const i=r(3325).D.spa,o=["click","submit","keypress","keydown","keyup","change"],a=999,s="fn-start",c="fn-end",u="cb-start",d="api-ixn-",f="remaining",l="interaction",h="spaNode",g="jsonpNode",p="fetch-start",m="fetch-done",v="fetch-body-",b="jsonp-end",y=n.Yu.ST,w="-start",x="-end",A="-body",E="cb"+x,T="jsTime",_="fetch"},5938:(e,t,r)=>{r.d(t,{W:()=>o});var n=r(5763),i=r(2177);class o{constructor(e,t,r){this.agentIdentifier=e,this.aggregator=t,this.ee=i.ee.get(e,(0,n.OP)(this.agentIdentifier).isolatedBacklog),this.featureName=r,this.blocked=!1}}},9144:(e,t,r)=>{r.d(t,{j:()=>m});var n=r(3325),i=r(5763),o=r(5546),a=r(2177),s=r(7894),c=r(8e3),u=r(3960),d=r(385),f=r(50),l=r(3081),h=r(8632);function g(){const e=(0,h.gG)();["setErrorHandler","finished","addToTrace","inlineHit","addRelease","addPageAction","setCurrentRouteName","setPageViewName","setCustomAttribute","interaction","noticeError","setUserId"].forEach((t=>{e[t]=function(){for(var r=arguments.length,n=new Array(r),i=0;i 1?r-1:0),i=1;i {e.exposed&&e.api[t]&&o.push(e.api[t](...n))})),o.length>1?o:o[0]}(t,...n)}}))}var p=r(2587);function m(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},m=arguments.length>2?arguments[2]:void 0,v=arguments.length>3?arguments[3]:void 0,{init:b,info:y,loader_config:w,runtime:x={loaderType:m},exposed:A=!0}=t;const E=(0,h.gG)();y||(b=E.init,y=E.info,w=E.loader_config),(0,i.Dg)(e,b||{}),(0,i.GE)(e,w||{}),(0,i.sU)(e,x),y.jsAttributes??={},d.v6&&(y.jsAttributes.isWorker=!0),(0,i.CX)(e,y),g();const T=function(e,t){t||(0,c.R)(e,"api");const h={};var g=a.ee.get(e),p=g.get("tracer"),m="api-",v=m+"ixn-";function b(t,r,n,o){const a=(0,i.C5)(e);return null===r?delete a.jsAttributes[t]:(0,i.CX)(e,{...a,jsAttributes:{...a.jsAttributes,[t]:r}}),x(m,n,!0,o||null===r?"session":void 0)(t,r)}function y(){}["setErrorHandler","finished","addToTrace","inlineHit","addRelease"].forEach((e=>h[e]=x(m,e,!0,"api"))),h.addPageAction=x(m,"addPageAction",!0,n.D.pageAction),h.setCurrentRouteName=x(m,"routeName",!0,n.D.spa),h.setPageViewName=function(t,r){if("string"==typeof t)return"/"!==t.charAt(0)&&(t="/"+t),(0,i.OP)(e).customTransaction=(r||"http://custom.transaction")+t,x(m,"setPageViewName",!0)()},h.setCustomAttribute=function(e,t){let r=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if("string"==typeof e){if(["string","number"].includes(typeof t)||null===t)return b(e,t,"setCustomAttribute",r);(0,f.Z)("Failed to execute setCustomAttribute.\nNon-null value must be a string or number type, but a type of was provided."))}else(0,f.Z)("Failed to execute setCustomAttribute.\nName must be a string type, but a type of was provided."))},h.setUserId=function(e){if("string"==typeof e||null===e)return b("enduser.id",e,"setUserId",!0);(0,f.Z)("Failed to execute setUserId.\nNon-null value must be a string type, but a type of was provided."))},h.interaction=function(){return(new y).get()};var w=y.prototype={createTracer:function(e,t){var r={},i=this,a="function"==typeof t;return(0,o.p)(v+"tracer",[(0,s.z)(),e,r],i,n.D.spa,g),function(){if(p.emit((a?"":"no-")+"fn-start",[(0,s.z)(),i,a],r),a)try{return t.apply(this,arguments)}catch(e){throw p.emit("fn-err",[arguments,this,"string"==typeof e?new Error(e):e],r),e}finally{p.emit("fn-end",[(0,s.z)()],r)}}}};function x(e,t,r,i){return function(){return(0,o.p)(l.xS,["API/"+t+"/called"],void 0,n.D.metrics,g),i&&(0,o.p)(e+t,[(0,s.z)(),...arguments],r?null:this,i,g),r?void 0:this}}function A(){r.e(439).then(r.bind(r,7438)).then((t=>{let{setAPI:r}=t;r(e),(0,c.L)(e,"api")})).catch((()=>(0,f.Z)("Downloading runtime APIs failed...")))}return["actionText","setName","setAttribute","save","ignore","onEnd","getContext","end","get"].forEach((e=>{w[e]=x(v,e,void 0,n.D.spa)})),h.noticeError=function(e,t){"string"==typeof e&&(e=new Error(e)),(0,o.p)(l.xS,["API/noticeError/called"],void 0,n.D.metrics,g),(0,o.p)("err",[e,(0,s.z)(),!1,t],void 0,n.D.jserrors,g)},d.il?(0,u.b)((()=>A()),!0):A(),h}(e,v);return(0,h.Qy)(e,T,"api"),(0,h.Qy)(e,A,"exposed"),(0,h.EZ)("activatedFeatures",p.T),T}},3325:(e,t,r)=>{r.d(t,{D:()=>n,p:()=>i});const n={ajax:"ajax",jserrors:"jserrors",metrics:"metrics",pageAction:"page_action",pageViewEvent:"page_view_event",pageViewTiming:"page_view_timing",sessionReplay:"session_replay",sessionTrace:"session_trace",spa:"spa"},i={[n.pageViewEvent]:1,[n.pageViewTiming]:2,[n.metrics]:3,[n.jserrors]:4,[n.ajax]:5,[n.sessionTrace]:6,[n.pageAction]:7,[n.spa]:8,[n.sessionReplay]:9}}},n={};function i(e){var t=n[e];if(void 0!==t)return t.exports;var o=n[e]={exports:{}};return r[e](o,o.exports,i),o.exports}i.m=r,i.d=(e,t)=>{for(var r in t)i.o(t,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},i.f={},i.e=e=>Promise.all(Object.keys(i.f).reduce(((t,r)=>(i.f[r](e,t),t)),[])),i.u=e=>(({78:"page_action-aggregate",147:"metrics-aggregate",242:"session-manager",317:"jserrors-aggregate",348:"page_view_timing-aggregate",412:"lazy-feature-loader",439:"async-api",538:"recorder",590:"session_replay-aggregate",675:"compressor",733:"session_trace-aggregate",786:"page_view_event-aggregate",873:"spa-aggregate",898:"ajax-aggregate"}[e]||e)+"."+{78:"ac76d497",147:"3dc53903",148:"1a20d5fe",242:"2a64278a",317:"49e41428",348:"bd6de33a",412:"2f55ce66",439:"30bd804e",538:"1b18459f",590:"cf0efb30",675:"ae9f91a8",733:"83105561",786:"06482edd",860:"03a8b7a5",873:"e6b09d52",898:"998ef92b"}[e]+"-1.236.0.min.js"),i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e={},t="NRBA:",i.l=(r,n,o,a)=>{if(e[r])e[r].push(n);else{var s,c;if(void 0!==o)for(var u=document.getElementsByTagName("script"),d=0;d {s.onerror=s.onload=null,clearTimeout(h);var i=e[r];if(delete e[r],s.parentNode&&s.parentNode.removeChild(s),i&&i.forEach((e=>e(n))),t)return t(n)},h=setTimeout(l.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=l.bind(null,s.onerror),s.onload=l.bind(null,s.onload),c&&document.head.appendChild(s)}},i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.j=364,i.p="https://js-agent.newrelic.com/",(()=>{var e={364:0,953:0};i.f.j=(t,r)=>{var n=i.o(e,t)?e[t]:void 0;if(0!==n)if(n)r.push(n[2]);else{var o=new Promise(((r,i)=>n=e[t]=[r,i]));r.push(n[2]=o);var a=i.p+i.u(t),s=new Error;i.l(a,(r=>{if(i.o(e,t)&&(0!==(n=e[t])&&(e[t]=void 0),n)){var o=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;s.message="Loading chunk "+t+" failed.\n("+o+": "+a+")",s.name="ChunkLoadError",s.type=o,s.request=a,n[1](s)}}),"chunk-"+t,t)}};var t=(t,r)=>{var n,o,[a,s,c]=r,u=0;if(a.some((t=>0!==e[t]))){for(n in s)i.o(s,n)&&(i.m[n]=s[n]);if(c)c(i)}for(t&&t(r);u {i.r(o);var e=i(3325),t=i(5763);const r=Object.values(e.D);function n(e){const n={};return r.forEach((r=>{n[r]=function(e,r){return!1!==(0,t.Mt)(r,"".concat(e,".enabled"))}(r,e)})),n}var a=i(9144);var s=i(5546),c=i(385),u=i(8e3),d=i(5938),f=i(3960),l=i(50);class h extends d.W{constructor(e,t,r){let n=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];super(e,t,r),this.auto=n,this.abortHandler,this.featAggregate,this.onAggregateImported,n&&(0,u.R)(e,r)}importAggregator(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(this.featAggregate||!this.auto)return;const r=c.il&&!0===(0,t.Mt)(this.agentIdentifier,"privacy.cookies_enabled");let n;this.onAggregateImported=new Promise((e=>{n=e}));const o=async()=>{let t;try{if(r){const{setupAgentSession:e}=await Promise.all([i.e(860),i.e(242)]).then(i.bind(i,3228));t=e(this.agentIdentifier)}}catch(e){(0,l.Z)("A problem occurred when starting up session manager. This page will not start or extend any session.",e)}try{if(!this.shouldImportAgg(this.featureName,t))return void(0,u.L)(this.agentIdentifier,this.featureName);const{lazyFeatureLoader:r}=await i.e(412).then(i.bind(i,8582)),{Aggregate:o}=await r(this.featureName,"aggregate");this.featAggregate=new o(this.agentIdentifier,this.aggregator,e),n(!0)}catch(e){(0,l.Z)("Downloading and initializing ".concat(this.featureName," failed..."),e),this.abortHandler?.(),n(!1)}};c.il?(0,f.b)((()=>o()),!0):o()}shouldImportAgg(r,n){return r!==e.D.sessionReplay||!1!==(0,t.Mt)(this.agentIdentifier,"session_trace.enabled")&&(!!n?.isNew||!!n?.state.sessionReplay)}}var g=i(7633),p=i(7894);class m extends h{static featureName=g.t9;constructor(r,n){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];if(super(r,n,g.t9,i),("undefined"==typeof PerformanceNavigationTiming||c.Tt)&&"undefined"!=typeof PerformanceTiming){const n=(0,t.OP)(r);n[g.Dz]=Math.max(Date.now()-n.offset,0),(0,f.K)((()=>n[g.qw]=Math.max((0,p.z)()-n[g.Dz],0))),(0,f.b)((()=>{const t=(0,p.z)();n[g.OJ]=Math.max(t-n[g.Dz],0),(0,s.p)("timing",["load",t],void 0,e.D.pageViewTiming,this.ee)}))}this.importAggregator()}}var v=i(1117),b=i(1284);class y extends v.w{constructor(e){super(e),this.aggregatedData={}}store(e,t,r,n,i){var o=this.getBucket(e,t,r,i);return o.metrics=function(e,t){t||(t={count:0});return t.count+=1,(0,b.D)(e,(function(e,r){t[e]=w(r,t[e])})),t}(n,o.metrics),o}merge(e,t,r,n,i){var o=this.getBucket(e,t,n,i);if(o.metrics){var a=o.metrics;a.count+=r.count,(0,b.D)(r,(function(e,t){if("count"!==e){var n=a[e],i=r[e];i&&!i.c?a[e]=w(i.t,n):a[e]=function(e,t){if(!t)return e;t.c||(t=x(t.t));return t.min=Math.min(e.min,t.min),t.max=Math.max(e.max,t.max),t.t+=e.t,t.sos+=e.sos,t.c+=e.c,t}(i,a[e])}}))}else o.metrics=r}storeMetric(e,t,r,n){var i=this.getBucket(e,t,r);return i.stats=w(n,i.stats),i}getBucket(e,t,r,n){this.aggregatedData[e]||(this.aggregatedData[e]={});var i=this.aggregatedData[e][t];return i||(i=this.aggregatedData[e][t]={params:r||{}},n&&(i.custom=n)),i}get(e,t){return t?this.aggregatedData[e]&&this.aggregatedData[e][t]:this.aggregatedData[e]}take(e){for(var t={},r="",n=!1,i=0;i t.max&&(t.max=e),e 2&&void 0!==arguments[2])||arguments[2];super(e,r,j.t,n),c.il&&((0,t.OP)(e).initHidden=Boolean("hidden"===document.visibilityState),(0,N.N)((()=>(0,s.p)("docHidden",[(0,p.z)()],void 0,j.t,this.ee)),!0),(0,O.bP)("pagehide",(()=>(0,s.p)("winPagehide",[(0,p.z)()],void 0,j.t,this.ee))),this.importAggregator())}}var P=i(3081);class C extends h{static featureName=P.t9;constructor(e,t){let r=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];super(e,t,P.t9,r),this.importAggregator()}}var R,I=i(2210),k=i(1214),H=i(2177),L={};try{R=localStorage.getItem("__nr_flags").split(","),console&&"function"==typeof console.log&&(L.console=!0,-1!==R.indexOf("dev")&&(L.dev=!0),-1!==R.indexOf("nr_dev")&&(L.nrDev=!0))}catch(e){}function z(e){try{L.console&&z(e)}catch(e){}}L.nrDev&&H.ee.on("internal-error",(function(e){z(e.stack)})),L.dev&&H.ee.on("fn-err",(function(e,t,r){z(r.stack)})),L.dev&&(z("NR AGENT IN DEVELOPMENT MODE"),z("flags: "+(0,b.D)(L,(function(e,t){return e})).join(", ")));var M=i(6660);class B extends h{static featureName=M.t;constructor(r,n){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];super(r,n,M.t,i),this.skipNext=0;try{this.removeOnAbort=new AbortController}catch(e){}const o=this;o.ee.on("fn-start",(function(e,t,r){o.abortHandler&&(o.skipNext+=1)})),o.ee.on("fn-err",(function(t,r,n){o.abortHandler&&!n[M.A]&&((0,I.X)(n,M.A,(function(){return!0})),this.thrown=!0,(0,s.p)("err",[n,(0,p.z)()],void 0,e.D.jserrors,o.ee))})),o.ee.on("fn-end",(function(){o.abortHandler&&!this.thrown&&o.skipNext>0&&(o.skipNext-=1)})),o.ee.on("internal-error",(function(t){(0,s.p)("ierr",[t,(0,p.z)(),!0],void 0,e.D.jserrors,o.ee)})),this.origOnerror=c._A.onerror,c._A.onerror=this.onerrorHandler.bind(this),c._A.addEventListener("unhandledrejection",(t=>{const r=function(e){let t="Unhandled Promise Rejection: ";if(e instanceof Error)try{return e.message=t+e.message,e}catch(t){return e}if(void 0===e)return new Error(t);try{return new Error(t+(0,D.P)(e))}catch(e){return new Error(t)}}(t.reason);(0,s.p)("err",[r,(0,p.z)(),!1,{unhandledPromiseRejection:1}],void 0,e.D.jserrors,this.ee)}),(0,O.m$)(!1,this.removeOnAbort?.signal)),(0,k.gy)(this.ee),(0,k.BV)(this.ee),(0,k.em)(this.ee),(0,t.OP)(r).xhrWrappable&&(0,k.Kf)(this.ee),this.abortHandler=this.#e,this.importAggregator()}#e(){this.removeOnAbort?.abort(),this.abortHandler=void 0}onerrorHandler(t,r,n,i,o){"function"==typeof this.origOnerror&&this.origOnerror(...arguments);try{this.skipNext?this.skipNext-=1:(0,s.p)("err",[o||new F(t,r,n),(0,p.z)()],void 0,e.D.jserrors,this.ee)}catch(t){try{(0,s.p)("ierr",[t,(0,p.z)(),!0],void 0,e.D.jserrors,this.ee)}catch(e){}}return!1}}function F(e,t,r){this.message=e||"Uncaught error with no additional information",this.sourceURL=t,this.line=r}let U=1;const q="nr@id";function G(e){const t=typeof e;return!e||"object"!==t&&"function"!==t?-1:e===c._A?0:(0,I.X)(e,q,(function(){return U++}))}function V(e){if("string"==typeof e&&e.length)return e.length;if("object"==typeof e){if("undefined"!=typeof ArrayBuffer&&e instanceof ArrayBuffer&&e.byteLength)return e.byteLength;if("undefined"!=typeof Blob&&e instanceof Blob&&e.size)return e.size;if(!("undefined"!=typeof FormData&&e instanceof FormData))try{return(0,D.P)(e).length}catch(e){return}}}var X=i(7243);class W{constructor(e){this.agentIdentifier=e,this.generateTracePayload=this.generateTracePayload.bind(this),this.shouldGenerateTrace=this.shouldGenerateTrace.bind(this)}generateTracePayload(e){if(!this.shouldGenerateTrace(e))return null;var r=(0,t.DL)(this.agentIdentifier);if(!r)return null;var n=(r.accountID||"").toString()||null,i=(r.agentID||"").toString()||null,o=(r.trustKey||"").toString()||null;if(!n||!i)return null;var a=(0,_.M)(),s=(0,_.Ht)(),c=Date.now(),u={spanId:a,traceId:s,timestamp:c};return(e.sameOrigin||this.isAllowedOrigin(e)&&this.useTraceContextHeadersForCors())&&(u.traceContextParentHeader=this.generateTraceContextParentHeader(a,s),u.traceContextStateHeader=this.generateTraceContextStateHeader(a,c,n,i,o)),(e.sameOrigin&&!this.excludeNewrelicHeader()||!e.sameOrigin&&this.isAllowedOrigin(e)&&this.useNewrelicHeaderForCors())&&(u.newrelicHeader=this.generateTraceHeader(a,s,c,n,i,o)),u}generateTraceContextParentHeader(e,t){return"00-"+t+"-"+e+"-01"}generateTraceContextStateHeader(e,t,r,n,i){return i+"@nr=0-1-"+r+"-"+n+"-"+e+"----"+t}generateTraceHeader(e,t,r,n,i,o){if(!("function"==typeof c._A?.btoa))return null;var a={v:[0,1],d:{ty:"Browser",ac:n,ap:i,id:e,tr:t,ti:r}};return o&&n!==o&&(a.d.tk=o),btoa((0,D.P)(a))}shouldGenerateTrace(e){return this.isDtEnabled()&&this.isAllowedOrigin(e)}isAllowedOrigin(e){var r=!1,n={};if((0,t.Mt)(this.agentIdentifier,"distributed_tracing")&&(n=(0,t.P_)(this.agentIdentifier).distributed_tracing),e.sameOrigin)r=!0;else if(n.allowed_origins instanceof Array)for(var i=0;i 2&&void 0!==arguments[2])||arguments[2];super(r,n,Z.t,i),(0,t.OP)(r).xhrWrappable&&(this.dt=new W(r),this.handler=(e,t,r,n)=>(0,s.p)(e,t,r,n,this.ee),(0,k.u5)(this.ee),(0,k.Kf)(this.ee),function(r,n,i,o){function a(e){var t=this;t.totalCbs=0,t.called=0,t.cbTime=0,t.end=E,t.ended=!1,t.xhrGuids={},t.lastSize=null,t.loadCaptureCalled=!1,t.params=this.params||{},t.metrics=this.metrics||{},e.addEventListener("load",(function(r){_(t,e)}),(0,O.m$)(!1)),c.IF||e.addEventListener("progress",(function(e){t.lastSize=e.loaded}),(0,O.m$)(!1))}function s(e){this.params={method:e[0]},T(this,e[1]),this.metrics={}}function u(e,n){var i=(0,t.DL)(r);i.xpid&&this.sameOrigin&&n.setRequestHeader("X-NewRelic-ID",i.xpid);var a=o.generateTracePayload(this.parsedOrigin);if(a){var s=!1;a.newrelicHeader&&(n.setRequestHeader("newrelic",a.newrelicHeader),s=!0),a.traceContextParentHeader&&(n.setRequestHeader("traceparent",a.traceContextParentHeader),a.traceContextStateHeader&&n.setRequestHeader("tracestate",a.traceContextStateHeader),s=!0),s&&(this.dt=a)}}function d(e,t){var r=this.metrics,i=e[0],o=this;if(r&&i){var a=V(i);a&&(r.txSize=a)}this.startTime=(0,p.z)(),this.listener=function(e){try{"abort"!==e.type||o.loadCaptureCalled||(o.params.aborted=!0),("load"!==e.type||o.called===o.totalCbs&&(o.onloadCalled||"function"!=typeof t.onload)&&"function"==typeof o.end)&&o.end(t)}catch(e){try{n.emit("internal-error",[e])}catch(e){}}};for(var s=0;s 1?e[1]=i:e.push(i)}else e[0]&&e[0].headers&&s(e[0].headers,n)&&(this.dt=n);function s(e,t){var r=!1;return t.newrelicHeader&&(e.set("newrelic",t.newrelicHeader),r=!0),t.traceContextParentHeader&&(e.set("traceparent",t.traceContextParentHeader),t.traceContextStateHeader&&e.set("tracestate",t.traceContextStateHeader),r=!0),r}}function x(e,t){this.params={},this.metrics={},this.startTime=(0,p.z)(),this.dt=t,e.length>=1&&(this.target=e[0]),e.length>=2&&(this.opts=e[1]);var r,n=this.opts||{},i=this.target;"string"==typeof i?r=i:"object"==typeof i&&i instanceof Y?r=i.url:c._A?.URL&&"object"==typeof i&&i instanceof URL&&(r=i.href),T(this,r);var o=(""+(i&&i instanceof Y&&i.method||n.method||"GET")).toUpperCase();this.params.method=o,this.txSize=V(n.body)||0}function A(t,r){var n;this.endTime=(0,p.z)(),this.params||(this.params={}),this.params.status=r?r.status:0,"string"==typeof this.rxSize&&this.rxSize.length>0&&(n=+this.rxSize);var o={txSize:this.txSize,rxSize:n,duration:(0,p.z)()-this.startTime};i("xhr",[this.params,o,this.startTime,this.endTime,"fetch"],this,e.D.ajax)}function E(t){var r=this.params,n=this.metrics;if(!this.ended){this.ended=!0;for(var o=0;o 2&&void 0!==arguments[2])||arguments[2];super(e,t,we.t,r),this.importAggregator()}}new class{constructor(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:(0,_.ky)(16);c._A?(this.agentIdentifier=t,this.sharedAggregator=new y({agentIdentifier:this.agentIdentifier}),this.features={},this.desiredFeatures=new Set(e.features||[]),this.desiredFeatures.add(m),Object.assign(this,(0,a.j)(this.agentIdentifier,e,e.loaderType||"agent")),this.start()):(0,l.Z)("Failed to initial the agent. Could not determine the runtime environment.")}get config(){return{info:(0,t.C5)(this.agentIdentifier),init:(0,t.P_)(this.agentIdentifier),loader_config:(0,t.DL)(this.agentIdentifier),runtime:(0,t.OP)(this.agentIdentifier)}}start(){const t="features";try{const r=n(this.agentIdentifier),i=[...this.desiredFeatures];i.sort(((t,r)=>e.p[t.featureName]-e.p[r.featureName])),i.forEach((t=>{if(r[t.featureName]||t.featureName===e.D.pageViewEvent){const n=function(t){switch(t){case e.D.ajax:return[e.D.jserrors];case e.D.sessionTrace:return[e.D.ajax,e.D.pageViewEvent];case e.D.sessionReplay:return[e.D.sessionTrace];case e.D.pageViewTiming:return[e.D.pageViewEvent];default:return[]}}(t.featureName);n.every((e=>r[e]))||(0,l.Z)("".concat(t.featureName," is enabled but one or more dependent features has been disabled (").concat((0,D.P)(n),"). This may cause unintended consequences or missing data...")),this.features[t.featureName]=new t(this.agentIdentifier,this.sharedAggregator)}})),(0,T.Qy)(this.agentIdentifier,this.features,t)}catch(e){(0,l.Z)("Failed to initialize all enabled instrument classes (agent aborted) -",e);for(const e in this.features)this.features[e].abortHandler?.();const r=(0,T.fP)();return delete r.initializedAgents[this.agentIdentifier]?.api,delete r.initializedAgents[this.agentIdentifier]?.[t],delete this.sharedAggregator,r.ee?.abort(),delete r.ee?.get(this.agentIdentifier),!1}}}({features:[J,m,S,class extends h{static featureName=oe;constructor(t,r){if(super(t,r,oe,!(arguments.length>2&&void 0!==arguments[2])||arguments[2]),!c.il)return;const n=this.ee;let i;(0,k.QU)(n),this.eventsEE=(0,k.em)(n),this.eventsEE.on(se,(function(e,t){this.bstStart=(0,p.z)()})),this.eventsEE.on(ae,(function(t,r){(0,s.p)("bst",[t[0],r,this.bstStart,(0,p.z)()],void 0,e.D.sessionTrace,n)})),n.on(ce+ne,(function(e){this.time=(0,p.z)(),this.startPath=location.pathname+location.hash})),n.on(ce+ie,(function(t){(0,s.p)("bstHist",[location.pathname+location.hash,this.startPath,this.time],void 0,e.D.sessionTrace,n)}));try{i=new PerformanceObserver((t=>{const r=t.getEntries();(0,s.p)(te,[r],void 0,e.D.sessionTrace,n)})),i.observe({type:re,buffered:!0})}catch(e){}this.importAggregator({resourceObserver:i})}},C,xe,B,class extends h{static featureName=de;constructor(e,r){if(super(e,r,de,!(arguments.length>2&&void 0!==arguments[2])||arguments[2]),!c.il)return;if(!(0,t.OP)(e).xhrWrappable)return;try{this.removeOnAbort=new AbortController}catch(e){}let n,i=0;const o=this.ee.get("tracer"),a=(0,k._L)(this.ee),s=(0,k.Lg)(this.ee),u=(0,k.BV)(this.ee),d=(0,k.Kf)(this.ee),f=this.ee.get("events"),l=(0,k.u5)(this.ee),h=(0,k.QU)(this.ee),g=(0,k.Gm)(this.ee);function m(e,t){h.emit("newURL",[""+window.location,t])}function v(){i++,n=window.location.hash,this[ve]=(0,p.z)()}function b(){i--,window.location.hash!==n&&m(0,!0);var e=(0,p.z)();this[pe]=~~this[pe]+e-this[ve],this[ye]=e}function y(e,t){e.on(t,(function(){this[t]=(0,p.z)()}))}this.ee.on(ve,v),s.on(be,v),a.on(be,v),this.ee.on(ye,b),s.on(ge,b),a.on(ge,b),this.ee.buffer([ve,ye,"xhr-resolved"],this.featureName),f.buffer([ve],this.featureName),u.buffer(["setTimeout"+le,"clearTimeout"+fe,ve],this.featureName),d.buffer([ve,"new-xhr","send-xhr"+fe],this.featureName),l.buffer([me+fe,me+"-done",me+he+fe,me+he+le],this.featureName),h.buffer(["newURL"],this.featureName),g.buffer([ve],this.featureName),s.buffer(["propagate",be,ge,"executor-err","resolve"+fe],this.featureName),o.buffer([ve,"no-"+ve],this.featureName),a.buffer(["new-jsonp","cb-start","jsonp-error","jsonp-end"],this.featureName),y(l,me+fe),y(l,me+"-done"),y(a,"new-jsonp"),y(a,"jsonp-end"),y(a,"cb-start"),h.on("pushState-end",m),h.on("replaceState-end",m),window.addEventListener("hashchange",m,(0,O.m$)(!0,this.removeOnAbort?.signal)),window.addEventListener("load",m,(0,O.m$)(!0,this.removeOnAbort?.signal)),window.addEventListener("popstate",(function(){m(0,i>1)}),(0,O.m$)(!0,this.removeOnAbort?.signal)),this.abortHandler=this.#e,this.importAggregator()}#e(){this.removeOnAbort?.abort(),this.abortHandler=void 0}}],loaderType:"spa"})})(),window.NRBA=o})(); window.jQuery || document.write(' ') CKEDITOR_BASEPATH='https://f1000research.com/js/vendor/ckeditor/' window.reactTheme = 'research'; window.MathJax = { CommonHTML: { linebreaks: { automatic: true } }, 'HTML-CSS': { linebreaks: { automatic: true } }, SVG: { linebreaks: { automatic: true } }, AuthorInit: function() { MathJax.Hub.Register.MessageHook('End Process', function () { let timeout = false; // holder for timeout id const delay = 250; // delay after event is "complete" to run callback const reflowMath = function() { const dispFormulas = document.querySelectorAll('.disp-formula.panel'); if (!dispFormulas) { return; } for (const dispFormula of dispFormulas) { const child = dispFormula.querySelector('.MathJax_Preview').nextSibling.firstChild; const isMultiline = MathJax.Hub.getAllJax(dispFormula)[0].root.isMultiline; if (dispFormula.offsetWidth < child.offsetWidth || isMultiline) { MathJax.Hub.Queue(['Rerender', MathJax.Hub, dispFormula]); } } }; window.addEventListener('resize', function() { clearTimeout(timeout); // clear the timeout timeout = setTimeout(reflowMath, delay); // start timing for event "completion" }); }); }, }; if (window.location.hash == '#_=_'){ window.location = window.location.href.split('#')[0] } !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function() {n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)} ;if(!f._fbq)f._fbq=n; n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window, document,'script','https://connect.facebook.net/en_US/fbevents.js'); fbq('init', '1641728616063202'); fbq('track', "PixelInitialized", {}); (function(h,o,t,j,a,r){ h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)}; h._hjSettings={hjid:2318163,hjsv:6}; a=o.getElementsByTagName('head')[0]; r=o.createElement('script');r.async=1; r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv; a.appendChild(r); })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv='); search file_upload Submit your research search menu close search Browse Gateways & Collections How to Publish Submit your Research My Submissions Article Guidelines Article Guidelines (New Versions) Open Data, Software and Code Guidelines Open Data and Accessible Source Materials Guidelines (HSS) Open Data, Software and Code Guidelines (PSE) Prepublication Checks Production Process Posters and Slides Guidelines Document Guidelines Article Processing Charges Peer Review Finding Article Reviewers About How it Works For Reviewers Our Advisors Policies Glossary FAQs For Developers Newsroom Contact My Research Submissions Content and Tracking Alerts My Details Sign In file_upload Submit your research { "@context": "https://schema.org", "@type": "ScholarlyArticle", "mainEntityOfPage": { "@type": "WebPage", "@id": "https://f1000research.com/articles/15-33" }, "headline": "Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem", "datePublished": "2026-01-09T05:48:06", "dateModified": "2026-01-09T05:48:06", "author": [ { "@type": "Person", "name": "Xingyi Chen" }, { "@type": "Person", "name": "Michael Totty" }, { "@type": "Person", "name": "Stephanie Hicks" } ], "publisher": { "@type": "Organization", "name": "F1000Research", "logo": { "@type": "ImageObject", "url": "https://f1000research.com/img/AMP/F1000Research_image.png", "height": 480, "width": 60 } }, "image": { "@type": "ImageObject", "url": "https://f1000research.com/img/AMP/F1000Research_image.png", "height": 1200, "width": 150 }, "description": "Spatially-resolved transcriptomics (SRT) generates large and heterogeneous datasets where global (tissue-wide) quality control (QC) metrics often over-aggressively remove biologically meaningful regions or miss localized artifacts. Recently, spatially-aware QC metrics have been introduced in SpotSweeper, but this is limited to the R programming language, which makes it challenging to use these metrics within the Python/scverse ecosystem. Here, we present SpotSweeper-py, a Python equivalent package of SpotSweeper that computes neighborhood-aware z-scores for standard QC metrics such as total counts, log total counts, number of detected genes, and percentage of mitochondrial counts. We demonstrate the performance and usability of SpotSweeper-py on two public datasets from the 10x Genomics Visium and VisiumHD platforms. This implementation of local spatially-aware QC metrics enables direct integration with Python/scverse ecosystem, reduces false positives from global quality control while preserving tissue-specific architecture. Plotting utilities are also included for quick visualizations of flagged outliers. By making robust local QC accessible in Python, SpotSweeper-py strengthens the reliability of pipelines for analyzing SRT data. The open-source software is available on PyPI (https://pypi.org/project/spotsweeper)." } { "@context": "http://schema.org", "@type": "BreadcrumbList", "itemListElement": [ { "@type": "ListItem", "position": "1", "item": { "@id": "https://f1000research.com/", "name": "Home" } }, { "@type": "ListItem", "position": "2", "item": { "@id": "https://f1000research.com/browse/articles", "name": "Browse" } }, { "@type": "ListItem", "position": "3", "item": { "@id": "https://f1000research.com/articles/15-33", "name": "Spotsweeper-py: spatially-aware quality control metrics for spatial..." } } ] } Home Browse Spotsweeper-py: spatially-aware quality control metrics for spatial... ALL Metrics - Views Downloads Get PDF Get XML Cite How to cite this article Chen X, Totty M and Hicks S. Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.12688/f1000research.175162.1 ) NOTE: If applicable, it is important to ensure the information in square brackets after the title is included in all citations of this article. Close Copy Citation Details Export Export Citation Sciwheel EndNote Ref. Manager Bibtex ProCite Sente EXPORT Select a format first Track Share ▬ ✚ Software Tool Article Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] Xingyi Chen https://orcid.org/0009-0008-7226-2022 1 , Michael Totty 2 , Stephanie Hicks 2-7 Xingyi Chen https://orcid.org/0009-0008-7226-2022 1 , Michael Totty 2 , Stephanie Hicks 2-7 PUBLISHED 09 Jan 2026 Author details Author details 1 Department of Applied Math and Statistics, Johns Hopkins University, Baltimore, Maryland, USA 2 Department of Biostatistics, Johns Hopkins University Bloomberg School of Public Health, Baltimore, Maryland, USA 3 Department of Biomedical Engineering, Johns Hopkins University, Baltimore, Maryland, USA 4 Center for Imaging Science, Johns Hopkins University, Baltimore, Maryland, USA 5 Johns Hopkins Kavli Neuroscience Discovery Institute, Johns Hopkins University, Baltimore, Maryland, USA 6 Center for Computational Biology, Johns Hopkins University, Baltimore, Maryland, USA 7 Malone Center for Engineering in Healthcare, Johns Hopkins University, Baltimore, Maryland, USA Xingyi Chen Roles: Conceptualization, Data Curation, Formal Analysis, Investigation, Methodology, Software, Visualization, Writing – Original Draft Preparation, Writing – Review & Editing Michael Totty Roles: Formal Analysis, Methodology, Writing – Review & Editing Stephanie Hicks Roles: Conceptualization, Project Administration, Supervision, Writing – Original Draft Preparation, Writing – Review & Editing OPEN PEER REVIEW DETAILS REVIEWER STATUS This article is included in the Bioinformatics gateway. This article is included in the Python collection. Abstract Spatially-resolved transcriptomics (SRT) generates large and heterogeneous datasets where global (tissue-wide) quality control (QC) metrics often over-aggressively remove biologically meaningful regions or miss localized artifacts. Recently, spatially-aware QC metrics have been introduced in SpotSweeper, but this is limited to the R programming language, which makes it challenging to use these metrics within the Python/scverse ecosystem. Here, we present SpotSweeper-py, a Python equivalent package of SpotSweeper that computes neighborhood-aware z -scores for standard QC metrics such as total counts, log total counts, number of detected genes, and percentage of mitochondrial counts. We demonstrate the performance and usability of SpotSweeper-py on two public datasets from the 10x Genomics Visium and VisiumHD platforms. This implementation of local spatially-aware QC metrics enables direct integration with Python/scverse ecosystem, reduces false positives from global quality control while preserving tissue-specific architecture. Plotting utilities are also included for quick visualizations of flagged outliers. By making robust local QC accessible in Python, SpotSweeper-py strengthens the reliability of pipelines for analyzing SRT data. The open-source software is available on PyPI (https://pypi.org/project/spotsweeper). READ ALL READ LESS Keywords spatially-resolved transcriptomics, quality control, Python, scverse Corresponding Author(s) Stephanie Hicks ( [email protected] ) Close Corresponding author: Stephanie Hicks Competing interests: No competing interests were disclosed. Grant information: This project was supported by NIH/NIGMS R35GM150671 (SCH) and NIH/NIMH F32MH13562 (MT). Copyright: © 2026 Chen X et al . This is an open access article distributed under the terms of the Creative Commons Attribution License , which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. How to cite: Chen X, Totty M and Hicks S. Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.12688/f1000research.175162.1 ) First published: 09 Jan 2026, 15 :33 ( https://doi.org/10.12688/f1000research.175162.1 ) Latest published: 09 Jan 2026, 15 :33 ( https://doi.org/10.12688/f1000research.175162.1 ) 1. Introduction Spatially-resolved transcriptomics (SRT) technologies have advanced rapidly in recent years, enabling high-throughput mapping of gene expression in complex tissues. Modern SRT platforms include diverse methodologies - from sequencing-based barcoding (e.g. Slide-seq2, 10x Genomics Visium and Visium HD) to imaging-based technologies (e.g. MERFISH, 10x Xenium). Increasingly large and complex spatial datasets are being generated from such technologies. 1 – 3 These technological innovations provide un-precedented inside into tissue structure and cellular interactions, but they also warrant the need for robust spatially-aware quality control (QC) to ensure reliability and intepretability of downstream analyses. Historically, computational pipelines for SRT have adopted QC metrics directly from single-cell RNA-seq workflows. Such global QC metrics, including total gene expression counts, number of detected genes, and percentage of reads from mitochondrial genes, can be applied across all spatial coordinates (spots or cells) in the tissue to flag low-quality artifacts. 4 – 6 However, recent work has shown that these global QC metrics may remove biologically meaningful regions as they were not designed for SRT data. In contrast, local QC approaches have been developed to address those gaps. 7 – 11 Notably, SpotSweeper introduced a QC method that utilizes each spot’s neighbors to assess quality in a spatially-aware context. 7 By comparing a given spot’s QC metrics (total counts, gene count, mitochondrial fraction) to the local mean of those metrics in neighboring spots, SpotSweeper flags local outliers (low-quality spots with respect to neighbors) and detects broader regional artifacts that traditional QC pipelines would not detect. This method, validated across SRT technologies, demonstrated its utility and performance over global metrics. However, SpotSweeper is currently implemented as R/Bioconductor package. Meanwhile, the Python ecosystem for single-cell and spatial transcriptomics has been growing rapidly in the past years, driven by libraries such as AnnData, Squidpy, and SpatialData under the scverse ecosystem. 12 – 16 These powerful tools allow seamless integration of spatial data with major Python workflows for downstream analysis, visualization, and machine learning. In this manuscript, we bridge the gap by introducing SpotSweeper-py, a Python implementation of the SpotSweeper R/Bioconductor package. SpotSweeper-py is an open-source package available on the Python package index (PyPI) ( https://pypi.org/project/spotsweeper ), enabling wide adoption and usability to the Python ecosystem. In the following sections, we demonstrate the design of SpotSweeper-py, its integration with major Python pipelines, and a couple use cases across different SRT platforms. By bringing spatially-aware QC into the Python ecosystem, SpotSweeper-py aims to ensure that high-quality data strengthens the robustness of SRT data analysis. 2. Use cases of SpotSweeper-py with public SRT datasets Here, we provide a brief summary of the methodology implemented in SpotSweeper 7 and now SpotSweeper-py. For each spatial coordinate, the method computes a neighborhood-aware robust z -score for each spot. Specifically, the method compares the QC metric at a given spatial coordinate to the median of its k -nearest neighbors, and scales by the median absolute deviation (MAD) based on all spatial coordinates in the neighborhood. Local outliers are then flagged one or two sided based on user-defined cutoffs. The package directly integrates with AnnData 13 objects in the Python/scverse ecosystem, produces z -scores and outlier indicator flags for each spot, and includes plotting utilities both in-line and PDF reporting. 2.1 SpotSweeper-Py improves quality control using 10x Genomics Visium data First, we considered a human breast cancer sample profiled using the 10x Genomics Visium CytAssist FFPE platform. 17 This tissue section had 4,169 spots after loading counts into an AnnData 13 object, annotating mitochondrial genes with MT- prefixes, and computing standard QC metrics using Scanpy. 16 We first considered the log-transformed total UMI counts as the QC metric, where the metric illustrated broad gradients with some tissue gaps and boundary effects typical for a FFPE tissue block ( Figure 1A ). When we consider outliers using a global threshold, specifically the median absolute deviations (MADs) across all spots with 3 MADs, we found that this picks up large and contiguous regions that have systematically low counts, even when those regions are locally homogeneous and plausibly biologically meaningful ( Figure 1B ). In contrast, when we identified outliers using SpotSweeper-py with 36 nearest neighbors and a robust MAD z -score cutoff of 3, we found that it revealed small degraded areas (e.g. micro-tears) while mostly retaining surrounding tissue ( Figure 1C ). Specifically, the global (vs local) QC approach (using log total UMI counts) flagged as low-quality 5.49% (229/4169) compared to 1.13% (47/4169) of total spots, respectively. Notably, global QC approaches using the raw (or observed) total counts flagged 0 low-quality outliers in the dataset. This can happen when the raw counts are skewed enough that the global 3 MAD lower-tail threshold falls below all observed values, which is why we recommend analyzing log normalized total counts. The contrast in treatment of regions with systematically low counts captures the core benefits of spatially aware QC. Figure 1. SpotSweeper-py improves quality control using the log-transformed UMI counts as the QC metric. Using a human breast cancer sample profiled using the 10x Genomics Visium CytAssist FFPE platform, (A) a spot plot of the log total UMI counts. The black represents bins with zero total UMI counts. (B) Detecting low-quality spots global QC thresholds (spots with counts < 3 MADs below the global mean shown in red). (C) Detecting low-quality spots using SpotSweeper-py with local QC metrics. This extends to other QC metrics for SRT data. For example, we see similar patterns of detected outliers across the tissue with the number of detected genes ( Figure 2A ). We found that global QC metrics flagged 3.60% spots (150/4169) as low-quality ( Figure 2B ), compared to 1.92% spots (80/4169) using local QC metrics ( Figure 2C ). Stated another way, global thresholding often discards large regions that are likely biologically meaningful. When using the percent of reads mapping to the mitochondrial genome ( Figure 2D ), we find that global outliers flag as low-quality 14.58% for percent mitochondrial counts (608/4169), which is primarily targeted on the the left region of the tissue, compared to 0.38% (16/4169) of spots flagged as low-quality using local outliers ( Figure 2E-F ). These tend to form small, coherent clusters where mitochondrial content spikes relative to neighbors, corresponding with local technical artifacts. When combining the three QC metrics, we found that there was a marked drop from 20.03% (global) to 2.42% (local) outliers. Similar to our previous findings, we show here that SpotSweeper-py retains biologically meaningful spots. 7 Figure 2. SpotSweeper-py improves quality control using the number of detected genes and the percent of reads mapping to the mitochondrial genome as the QC metric. Similar Figure 1 , we used the same human breast cancer sample profiled using the 10x Genomics Visium CytAssist FFPE platform. (A) a spot plot of the number of detected genes. (B) Detecting low-quality spots global QC thresholds (spots with counts < 3 MADs below the global median shown in red). (C) Detecting low-quality spots using SpotSweeper-py with local QC metrics. (D-F) Similar to A-C, but using the percent of reads mapping to the mitochrondial genome. In addition, we examined the distributions of local z -scores for standard QC metrics, stratified by whether each spot was flagged as an outlier by either global ( Figure 3A ) or local ( Figure 3B ) QC thresholds ( Table 1 ). Using global thresholds, spots labeled as outliers do not consistently correspond to extremes of the local z-score distributions. This reflects the fact that global cutoffs identify spots with extreme absolute QC values, but may fail to detect outlying spots whose deviations are defined relative to their local tissue neighborhood. Conversely, when applying local thresholds, spots flagged as outliers display clear shifts toward the tails of the local z -score distributions, demonstrating that the local method captures neighborhood-specific anomalies that are not apparent at the tissue level. Together, these comparisons highlight that local QC metrics identify biologically meaningful, spatially contextualized outliers that global thresholds may overlook and misclassify. Figure 3. Violin plots comparing outliers and non-outliers flagged by global QC and SpotSweeper QC across multiple QC metrics for a Visium sample. (A) Global QC: total counts, log total counts, percent mitochondrial counts, and number of genes detected. (B) Local QC (SpotSweeper): same metrics with neighborhood-based thresholds. Local z -score distributions are shown between outliers (orange) and non-outliers (blue) for each metric. Table 1. Outlier rates on the Visium CytAssist FFPE breast cancer sample ( n = 4169). Global: median ± 3 MAD (lower-tail for counts/genes; higher-tail for mitochondrial). Local: SpotSweeper-py ( k = 36, cutoff = 3). Metric (direction) Global Local Total counts (lower) 0.00% (0/4169) 0.98% (41/4169) Log total counts (lower) 5.49% (229/4169) 1.13% (47/4169) Genes detected (lower) 3.60% (150/4169) 1.92% (80/4169) % mitochondrial (higher) 14.58% (608/4169) 0.38% (16/4169) Importantly, these differences also manifest spatially: spots mis-flagged by global QC form large contiguous regions that SpotSweeper-py successfully preserves, whereas spots uniquely flagged by local QC appear more spatially scattered ( Figure 4 ). Figure 4. Spatial distribution of discrepancy spots between global and local QC methods for a Visium sample. Red dots mark spots flagged by global QC but not by any local SpotSweeper-py metric, while blue dots mark spots flagged only by local QC. Red discrepancies often appear in contiguous spatial regions, indicating areas that global QC mis-flags but SpotSweeper-py preserves. 2.2 SpotSweeper-Py improves quality control using 10x Genomics Visium HD data We then analyzed a public human breast cancer section profiled with the 10x Genomics VisiumHD Cy-tAssist FFPE platform. 18 Counts were loaded with spatialdata-io, 15 mitochondrial genes (prefix MT-) were annotated, and standard QC metrics were computed with visiumhd-utils (built on Scanpy ). 16 We primarily used the 8 μ m bin level (2 μ m results in the next use case). The processed 8 μ m AnnData object contained n = 663857 bins. As in the Visium analysis, we contrasted global thresholding (median ± 3 MAD; lower-tail for counts/genes; upper-tail for mitochondrial fraction) with SpotSweeper-py using k = 48 nearest neighbors (from suggestion in SpotSweeper 7 ) and a robust z -score cutoff of 3. In terms of computational speed, on a CPU node, local outlier detection and plotting for 8 μ m total counts completed in 35.7 s, illustrating practical runtimes even at HD scale. Next, we considered the log-transformed total UMI counts as the QC metric, where the metric exhibits round-shaped structures with localized low-signal areas across the tissue ( Figure 5A ). Using a global threshold, specifically 3 MADs below the global median, we found that outliers cluster in ex-tended low-signal regions and reflect dataset-wide tails rather than neighborhood deviations ( Figure 5B ). In contrast, SpotSweeper-py (with k = 48 nearest neighbors and a robust MAD z -score cutoff of 3) pin-points small degraded areas while largely retaining surrounding tissue ( Figure 5C ). Specifically, the global approach (using log total UMI counts) flagged as low-quality 2.14% (14211/663857) compared to 1.59% (10551/663857) using the local method. Notably, global thresholds using the raw (observed) total counts flagged 0 low-quality outliers in this dataset (0.00%), whereas the local method flagged 1.06% (7017/663857). This can occur when the raw counts are sufficiently skewed so that the lower-tail threshold falls below all observed values - again the reason we primarily analyze log-total counts. Figure 5. SpotSweeper-py improves quality control using the log-transformed UMI counts as the QC metric on VisiumHD (8 μ m). Using a human breast cancer sample profiled using the 10x Genomics Visium CytAssist FFPE platform, (A) a bin map of the log total UMI counts. The white represents bins with zero total UMI counts. (B) Detecting low-quality bins with a global threshold (bins with metric < − 3 MADs below the global median shown in red). (C) Detecting low-quality bins using SpotSweeper-py with local QC metrics. The same pattern extends across other QC metrics for SRT data ( Figure 6 ). Using the number of detected genes as the QC metric, global thresholds flagged 0.00% (0/663857) of bins ( Figure 6B ), whereas SpotSweeper-py flagged 1.09% (6995/663857), revealing focal anomalies with respect to their neighbor-hoods ( Figure 6C ). Using the percent of reads mapping to the mitochondrial genome ( Figure 6D ), global thresholds flagged 4.10% (27234/663857) of bins concentrated in broad low-signal regions, com-pared to 0.22% (1481/663857) flagged as local outliers by SpotSweeper-py ( Figure 6E-F ). These local outliers tend to form small, coherent clusters where mitochondrial content spikes relative to nearby bins, consistent with localized technical artifacts. When combining the three QC metrics, there is a drop from 6.14% (global) to 1.83% (local) of bins flagged as outliers ( Table 2 ), reflecting that many globally flagged bins lie in spatially homogeneous low-signal regions that are possibly biologically meaningful regions that spatially aware QC aims to retain. Figure 6. SpotSweeper-py improve quality control using the number of detected genes and the percent of reads mapping to the mitochondrial genome as the QC metric. Similar to Figure 5 , we used the same human breast cancer sample profiled using the 10x Genomics Visium HD CytAssist FFPE platform (8 μ m resolution). (A) a spot plot of the number of detected genes. (B) Detecting low-quality spots global QC thresholds (spots with counts <3 MADs below the global mean shown in red). (C) Detecting low-quality spots using SpotSweeper-py with local QC metrics. (D-F) Similar to A-C, but using the percent of reads mapping to the mitochrondial genome. Table 2. Outlier rates on the VisiumHD CytAssist FFPE breast cancer sample at 8um bins ( n = 663857). Global: median ± 3 MAD (lower-tail for counts/genes; higher-tail for mitochondrial). Local: SpotSweeper-py ( k = 48, cutoff = 3). Metric (direction) Global Local Total counts (lower) 0.00% (0/663857) 1.06% (7017/663857) Log total counts (lower) 2.14% (14211/663857) 1.59% (10551/663857) Genes detected (lower) 0.00% (0/663857) 1.09% (6995/663857) % mitochondrial (higher) 4.10% (27234/663857) 0.22% (1481/663857) Any metric flagged 6.14% (40754/663857) 1.83% (12173/663857) In addition, we compared the distributions of local z -scores for QC metrics stratified by whether a bin was flagged as an outlier under either global ( Figure 7A ) or local ( Figure 7B ) thresholds ( Table 2 ). When applying global thresholds, non-outlier and outlier groups show limited separation in their local z -score distributions for several metrics, and for total counts and number of detected genes, no global outliers were detected at all. This indicates that global criteria primarily capture extreme absolute QC values and may overlook spatially localized anomalies. In contrast, under local thresholds, bins flagged as outliers show clear shifts toward the extremes of the local z -score distributions across all metrics, demonstrating that the local method effectively identifies neighborhood-relative deviations that are not apparent when using global cutoffs. Figure 7. Violin plots comparing outliers and non-outliers flagged by global QC and SpotSweeper QC across multiple QC metrics for a Visium HD sample profiled at 8 μ m bin resolution. (A) Global QC: total counts, log total counts, percent mitochondrial counts, and number of genes detected. (B) Local QC (SpotSweeper): same metrics with neighborhood-based thresholds. Local z -score distributions are shown between outliers (orange) and non-outliers (blue) for each metric. 2.3 Extended use case: SpotSweeper-Py improves quality control using 10x Genomics Visium HD data profiled at 2 μ m level Finally, we evaluated SpotSweeper-py on the highest–resolution setting of the Visium HD CytAssist FFPE platform, using the 2 μ m bin representation of the same data analyzed above. After loading counts using spatialdata-io and computing standard QC metrics with visiumhd-utils , the resulting AnnData object contained n = 10535119 bins. As with the 8 μ m analysis, we compared global median ± 3 MAD thresholds to SpotSweeper-py with k = 48 nearest neighbors and a robust local z -score cutoff of 3. Despite the extremely large bin count, SpotSweeper-py completed local QC computations and visualization in under 15 minutes on a standard CPU node, demonstrating that neighborhood-based QC remains computationally feasible even at full Visium HD resolution. Figure 8 demonstrates that SpotSweeper-py remains effective even at the highest resolution of the Visium HD platform. Despite the greatly increased bin count, local QC continues to isolate small, spatially coherent defects without over-flagging large biologically meaningful regions. Across QC metrics (log total counts, number of genes, percent mitochondrial counts), SpotSweeper-py consistently identifies compact neighborhoods of outlier bins while preserving the majority of intact tissue. Figure 8. Outputs of SpotSweeper QC on a Visium HD tissue section profiled at 2 um resolution. Outlier spots (red) are identified using local thresholds (48 neighbors). (A) Low outliers based on log total UMI counts. (B) Low outliers based on number of genes detected. (C) High outliers based on percent mitochondrial counts. In contrast to the outlier maps, the distributional behavior of QC metrics at 2 μ m resolution further highlights the limitations of global thresholding. Under global QC, only the percent mitochondrial metric produced any outliers at all: total counts, log total counts, and number of detected genes showed no bins exceeding global 3-MAD cutoffs ( Figure 9A ). When examining the local z -score distributions ( Figure 9B ), bins identified by SpotSweeper-py show clear and consistent shifts toward the tails of their respective local z -score distributions. This separation demonstrates that local QC captures spatially aware deviations that define technical artifacts at a high resolution, whereas global QC merely flags significant global shifts. Figure 9. Violin plots comparing outliers and non-outliers flagged by global QC and SpotSweeper QC across multiple QC metrics for a Visium HD sample profiled at 2um bin resolution. (A) Global QC: total counts, log total counts, percent mitochondrial counts, and number of genes detected. (B) Local QC (SpotSweeper): same metrics with neighborhood-based thresholds. Local z -score distributions are shown between outliers (orange) and non-outliers (blue) for each metric. 3. Discussion Spatial transcriptomics data exhibit heterogeneity driven by both technical and biological factors, such as tissue boundaries, gradients, and tissue degradation. Therefore, applying global, dataset-wide thresholding to quality control metrics could remove the entirety of biologically significant regions or miss regions with small but important tissue defects. Our PyPI package, SpotSweeper-py, addresses this issue by building a spatially-aware quality control framework that identifies outliers based on local robust z -scores that integrates seamlessly into the Python/scverse ecosystem. When conducting analyses on datasets from two sample spot-based spatial transcriptomics technologies, Visium and Visium HD (at 8 μ m level), SpotSweeper-py reduced false positives with respect to outliers identified using global MAD thresholds and revealed small, spatially-aware artifacts consistent with biological structure. As the implementation of SpotSweeper-py integrates with scverse, SpatialData, and AnnData ecosystem in Python, the annotations for local outliers could easily transfer to downstream steps like clustering and differential expression. As a result, the reliability and interpretability of spatial transcriptomics data analyses could be improved in Python-based workflows. However, there are still some limitations to consider. SpotSweeper-py assumes accurate spatial coordinates and relatively uniform capture spot density. Any technical issues such as mis-stitching could significantly bias local statistics and may impact the performance of our algorithm by outputting false positives. It is also important to note that run-time and memory usage scales up as the number of spots and bins increases due to k -nearest neighbor construction. In our example use cases, the algorithm is feasible on a CPU - the runtime for a Visium section and a Visium HD section profiled on 8 μ m bins are under 1 minute. However, the runtime quickly scales up for the same Visium HD section, profiled at 2 μ m bins. The run-time could take up to 15 minutes for outlier calculations in that setting. Finally, the local outliers detected from SpotSweeper-py are indicators that solely based on sequencing metrics: additional histology evidence is also recommended before filtering. Here, we demonstrated how to control bias in outlier detection using SpotSweeper-py. Robust and neighborhood-aware statistics are computed based on the median and MAD, thus limiting the influence of extreme values and heavy-tailed distributions. Log(1+x) transforms are performed on UMI counts before conducting local outlier analysis in order to stabilize variance for count metrics. We also recommend investigating common outliers from all 4 QC metrics to reduce dependence on one particular metric. Looking to the future, SpotSweeper-py will be extended from just local outlier detection to include broader spatial artifact detection as implemented in SpotSweeper, 7 mirroring the functionalities in the implementation in R. As a result, we could expect additional identification of contiguous and systematic low-quality regions. Additional extension directions include multi-scale QC that includes outliers across neighborhood radii, dynamic neighbor number adjustment based on the shape of data, as well as additional interactive visualization and data reporting. Collectively, these developments aim to make local and spatially-aware QC a transparent and standard component of spatial transcriptomics analysis pipelines and therefore strengthen the credibility of downstream results. 4. Methods 4.1 Implementation of SpotSweeper-py 4.1.1 Overview and relationship to SpotSweeper (R) Our package, spotsweeper-py re-implements, in Python, the local QC outliers detection and plotting functionalities of the original SpotSweeper R package, which introduced spatially aware QC to detect both local outliers and regional artifacts in spatial transcriptomics outputs using standard QC metrics (e.g. library size, detected genes, mitochondrial fraction). Here, we focus on the local outlier component (parallel to localOutliers() function in the R package) and on plotting utilities. Our proposed Python package emphasizes a clean and lightweight Python API for local outlier detection and publication-ready visualization that integrates directly with major downstream workflows like AnnData/Scanpy, providing a Python-native option. 4.1.2 Essential data inputs local_outliers ( adata, metric= “ detected ” , direction = “ lower” , n_neighbors =36, sample_key = “ sample_id ” , log =True, cutoff =3.0, workers =1, coord_key = “ spatial” ) The core function to detect local outliers, operates on a single anndata. AnnData object and expects the following components to be available in the AnnData object. • Spatial Coordinates: adata.obsm [coord_key] (default: “spatial”) with shape (n_spots * 2). • Sample Identifier: adata.obsm [sample_key] (default: “sample_id”) to ensure neighbors are computed within independent samples. • QC Metrics: adata.obs [metric] (default: “detected”). This could be substituted with other columns, for instance total counts, log total counts, or mitochondrial ratio. Strict validity checks are performed on these columns. If the columns are missing, a KeyError will be raised. For invalid parameters in addition to those columns, a ValueError will be raised, preventing the program from running. Other parameters will be explained in sections below. 4.1.3 Neighborhood construction For each sample, the local outliers function learns a k -nearest neighbor (kNN) structure over spatial coordinates with scikit-learn’s nearest neighbor function. 19 We use the “auto” algorithm in scikit-learn package to let it choose the fastest model for different data samples. The algorithm will select between a tree-based or brute force strategy depending on data size and dimensionality. Neighbor indices are then returned using scikit-learn’s helper functions. Typical defaults use 36 as the neighborhood size (equivalent with the R version, generalizable across many spatial transcriptomics technologies), but this is modifiable via n_neighbors parameter in the local outliers function. There is also an opportunity for parallelization, to be controlled via workers parameter. It defaults to 1, which means no parallelization in computing. 4.1.4 Robust local z -score calculations and outlier determination Let x i be the (optionally log-transformed) QC value at spot i , and let N ( i ) denote the indices of its k spatial neighbors. We compute a modified/robust z -score: z i = 0.6745 ⋅ x i − median { x j : j ∈ N ( i ) } MAD { x j : j ∈ N ( i ) } where MAD, the median absolute deviation, takes the following form: MAD( S ) = median j ∈ N ( i) 1 | x j − median {x ℓ : ℓ ∈ N ( i ) }|. 1 If MAD is zero or non-finite (when we have degenerate neighborhoods), we conservatively set the robust z -score to be 0. Non-finite results are also handled and set to 0. Multiple pa-rameters in the local outlier functions control the flagging process, including direction, which determines whether flagging is one-sided (“higher” or “lower”), or two-sided (“both”) along with: • Higher: will flag outliers if z i > cutoff, • Lower: will flag outliers if z i cutoff. The default cutoff = 3.0 is conservative and corresponds to classic robust z -score practice. Both the cutoff value and direction could be modified via function parameters. 4.1.5 Log transform and processing with AnnData objects If the log parameter is set to True (by default), we apply log(1 + x ) to reduce the impact of extremely large outliers. We write the log-transformed results into a new column {metric}_log in adata.obs. Note we do not overwrite the column if it already exists in adata.obs. This process ensures reproducibility of the pre-processing pipeline. 4.1.6 Local outlier function output The function returns the updated AnnData object, with the following updated columns in adata.obs: • adata.obs[f"{metric}_z"] (float): stores per-spot robust z -scores. • adata.obs[f"{metric}_outliers"] (bool): stores whether the spot is flagged by the selected outlier direction and z -score cutoff. These columns are index-aligned with the input AnnData object. 4.1.7 Additional visualization utilities In addition to the main local outlier functions, we included two extra helper modules for visualizing the location of local outliers in a spatial context. The corresponding functions are: • plot_qc_metrics: returns a scatterplot of single sample spatial coordinates, with continuous color for the metric, and red borders for identified outliers. We set an equal aspect ratio, invert the y-axis to match conventional tissue orientation, and create the colormap via LinearSegmentedColormap using either default colors, or user-supplied colors. • plot_qc_pdf: similar to the previous plotting function, while writing all output figures to a multi-page PDF with customizable point size and figure dimensions. 4.1.8 Function validation and tests via pytest We have setup a pytest suite for the package. It verifies: • Exact robust z -score calculations on synthetic inputs. • Creation, correct length and type of newly created columns in adata. • Correct detection of an extreme outlier. • Correct log-transformation and column creation. • Correct error-handling for all cases. • Successful generation of a PDF in a temporary directory. Those tests ensure the local outlier function as well as the plotting functions could produce accurate and defensible outputs. 4.2 Operation 4.2.1 Minimal system requirements • Software: Python, preferably has a version newer than 3.9. • Operating System: Linux, Windows, or MacOS. • Hardware: CPU is enough to run the local outlier functions (no GPU required). The usage of GPU could be helpful for multi-sample analyses, or for technologies like VisiumHD, where we observe millions of bins. 4.2.2 Run-time package dependencies The packages should appear standard to major Python downstream analysis workflows: Core: anndata, numpy, pandas, scikit-learn, matplotlib. Testing: pytest 4.2.3 Workflow overview To start the pipeline, we load and prepare the datasets, in the form of AnnData objects. Specifically, we need to ensure that adata.obsm["spatial"], adata.obs["sample_id"] and a QC metric column (ex. adata.obs["detected"]) are present in the AnnData object. Then, we call the local outlier function to detect outliers. from spotsweeper. local_outliers import local_outliers adata = local_outliers ( adata, metric= “ detected ” , direction = “ both ” , n_neighbors =36, sample_key = “ sample_id ” , log =True, cutoff =3.0, workers =1, coord_key = “ spatial” ) This snippet annotates detected_z and detected_outliers in adata.obs. We could then use the boolean outlier column detected_outliers to flag or filter local outlier spots before further downstream processing such as normalization, clustering, or differential expression analysis. Optionally, we could visualize the result of local outlier detection: plot_qc_metrics (adata, sample = “ A” , metric= “ detected ” , outliers= “ detected_outliers ” ) This function could interactively show the figures in a Jupyter notebook, or return a plot of the sample as well as flagged outliers. from spotsweeper. plot_QCpdf import plot_qc_pdf plot_qc_pdf( adata, sample_id = “ sample_id ” , metric= “ detected ” , outliers= “ detected_outliers ” , colors =( “ white ” , “ black ” ), point_size =2.0, width =5, height =5, fname = “ qc_plots.pdf” ) Or, we could take advantage of plot_qc_pdf function to generate the plots on a PDF. 4.2.4 Parameter guidance for typical workflows There are a couple adjustable parameters in the local outlier functions. Generally, we make the following recommendations: • Neighbors (n_neighbors): start with 24–48. Smaller k sharpens locality but increases variance; larger k stabilizes estimates but may dilute local variance. • Direction (direction): “lower” for metrics where extremely low values imply poor quality (for example, number of detected genes), “higher” for metrics like mitochondrial fraction, “both” if either tail is undesirable. • Threshold (cutoff ): default 3.0; consider 2.5 or lower when the tissue is complex, and more outliers need to be considered even with low confidence. • Log transform (log): recommended True for heavy-tailed metrics. Data availability The datasets used for the analyses in this manuscript are publicly available and can be downloaded from https://www.10xgenomics.com/datasets/gene-and-protein-expression-library-of-human - breast-cancer-cytassist-ffpe-2-standard (Visium) and https://www.10xgenomics.com/datasets/visium-hd - cytassist-gene-expression-libraries-human-breast-cancer-ffpe-if (Visium HD). Software availability • Software available from: https://pypi.org/project/spotsweeper . • Source code available from: https://github.com/danielchen05/SpotSweeper_py . • Archived source code at time of publication (software package): Zenodo, DOI: 10.5281/zenodo.17958723 . • Archived source code at time of publication (analysis and figure reproduction code): Zenodo, DOI: 10.5281/zenodo.17958741 . • License: MIT License. SpotSweeper-py version 1.0.0 was used for all analyses reported in this manuscript. All analyses were performed using Python version 3.9. No proprietary software was required for data processing or analysis; all steps can be reproduced using open-source software. Code to reproduce all preprocessing, analyses, and figures is available at https://github.com/danielchen05/SpotSweeper_py_paper . Acknowledgments We thank the maintainers of the Joint High Performance Computing Exchange (JHPCE) compute cluster at Johns Hopkins Bloomberg School of Public Health for providing essential computing resources. We also thank Hicks lab for their constructive comments and suggestions. References 1. Marx V: Method of the Year: spatially resolved transcriptomics. Nat. Methods. Jan. 2021; 18 (1): 9–14. 1548-7091, 1548-7105. PubMed Abstract | Publisher Full Text Reference Source 2. Moses L, Pachter L: Museum of spatial transcriptomics. Nat. Methods. May 2022; 19 (5): 534–546. 1548-7091, 1548-7105. PubMed Abstract | Publisher Full Text Reference Source 3. Lim HJ, Wang Y, Buzdin A, et al. : A practical guide for choosing an optimal spatial transcriptomics technology from seven major commercially available options. BMC Genomics. Jan. 2025; 26 (1): 47. 1471-2164. PubMed Abstract | Publisher Full Text | Free Full Text 4. Luecken MD, Theis FJ: Current best practices in single-cell RNA-seq analysis: a tutorial. Mol. Syst. Biol. June 2019; 15 (6): e8746. 1744-4292. PubMed Abstract | Publisher Full Text | Free Full Text 5. Osorio D, Cai JJ: Systematic determination of the mitochondrial proportion in human and mice tissues for single-cell RNA-sequencing data quality control. Bioinformatics. May 2021; 37 (7): 963–967. 1367-4803, 1367-4811. PubMed Abstract | Publisher Full Text | Free Full Text Reference Source 6. Yates J, Kraft A, Boeva V: Filtering cells with high mitochondrial content depletes viable metabolically altered malignant cell populations in cancer single-cell studies. Genome Biol. Apr. 2025; 26 (1): 91. 1474-760X. PubMed Abstract | Publisher Full Text | Free Full Text 7. Totty M, Hicks SC, Guo B: SpotSweeper: spatially aware quality control for spatial transcrip-tomics. Nat. Methods. July 2025; 22 (7): 1520–1530. 1548-7091, 1548-7105. PubMed Abstract | Publisher Full Text | Free Full Text Reference Source 8. Ni Z, Prasad A, Chen S, et al. : SpotClean adjusts for spot swapping in spatial transcriptomics data. Nat. Commun. May 2022; 13 (1): 2971. 2041-1723. PubMed Abstract | Publisher Full Text | Free Full Text Reference Source 9. Kummerfeld E, Williams L, Wang Y, et al. : Artifacts in spatial transcriptomics data: their detection, importance, prevalence, and prevention. Brief. Bioinform. July 2025; 26 (4): bbaf306. 1467-5463, 1477-4054. PubMed Abstract | Publisher Full Text | Free Full Text 10. Wang Y, Song B, Wang S, et al. : Sprod for de-noising spatially resolved transcriptomics data based on position and image information. Nat. Methods. Aug. 2022; 19 (8): 950–958. 1548-7091, 1548-7105. PubMed Abstract | Publisher Full Text | Free Full Text Reference Source 11. Mao G, Yang Y, Luo Z, et al. : SpatialQC: automated quality control for spatial transcriptome data. Bioinformatics. Aug. 2024; 40 (8): btae458. 1367-4811. PubMed Abstract | Publisher Full Text | Free Full Text 12. Wolf FA, Angerer P, Theis FJ: SCANPY: large-scale single-cell gene expression data analysis. Genome Biol. Dec. 2018; 19 (1): 15. 1474-760X. PubMed Abstract | Publisher Full Text | Free Full Text 13. Virshup I, Rybakov S, Theis FJ, et al. : anndata: Access and store anno-tated data matrices. Journal of Open Source Software. Sept. 2024; 9 (101): 4371. 2475-9066. Publisher Full Text 14. Palla G, Spitzer H, Klein M, et al. : Squidpy: a scalable framework for spatial omics analysis. Nat. Methods. Feb. 2022; 19 (2): 171–178. 1548-7091, 1548-7105. PubMed Abstract | Publisher Full Text | Free Full Text 15. Marconato L, Palla G, Yamauchi KA, et al. : SpatialData: an open and universal data framework for spatial omics. Nat. Methods. Jan. 2025; 22 (1): 58–62. 1548-7091, 1548-7105. PubMed Abstract | Free Full Text Publisher Full Text | Reference Source 16. Virshup I, Bredikhin D, Heumos L, et al. : The scverse project provides a computational ecosystem for single-cell omics data analysis. Nat. Biotechnol. May 2023; 41 (5): 604–606. 1087-0156, 1546-1696. PubMed Abstract | Publisher Full Text Reference Source 17. Visium CytAssist Gene and Protein Expression Library of Human Breast Cancer, IF, 6.5 mm (FFPE). http 18. Visium HD Spatial Gene Expression Library, Human Breast Cancer, IF (FFPE). http 19. 1.6. Nearest Neighbors. http Comments on this article Comments (0) Version 1 VERSION 1 PUBLISHED 09 Jan 2026 ADD YOUR COMMENT Comment Author details Author details 1 Department of Applied Math and Statistics, Johns Hopkins University, Baltimore, Maryland, USA 2 Department of Biostatistics, Johns Hopkins University Bloomberg School of Public Health, Baltimore, Maryland, USA 3 Department of Biomedical Engineering, Johns Hopkins University, Baltimore, Maryland, USA 4 Center for Imaging Science, Johns Hopkins University, Baltimore, Maryland, USA 5 Johns Hopkins Kavli Neuroscience Discovery Institute, Johns Hopkins University, Baltimore, Maryland, USA 6 Center for Computational Biology, Johns Hopkins University, Baltimore, Maryland, USA 7 Malone Center for Engineering in Healthcare, Johns Hopkins University, Baltimore, Maryland, USA Xingyi Chen Roles: Conceptualization, Data Curation, Formal Analysis, Investigation, Methodology, Software, Visualization, Writing – Original Draft Preparation, Writing – Review & Editing Michael Totty Roles: Formal Analysis, Methodology, Writing – Review & Editing Stephanie Hicks Roles: Conceptualization, Project Administration, Supervision, Writing – Original Draft Preparation, Writing – Review & Editing Competing interests No competing interests were disclosed. Grant information This project was supported by NIH/NIGMS R35GM150671 (SCH) and NIH/NIMH F32MH13562 (MT). Article Versions (1) version 1 Published: 09 Jan 2026, 15:33 https://doi.org/10.12688/f1000research.175162.1 Copyright © 2026 Chen X et al . This is an open access article distributed under the terms of the Creative Commons Attribution License , which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. Download Export To Sciwheel Bibtex EndNote ProCite Ref. Manager (RIS) Sente metrics Views Downloads F1000Research - - PubMed Central info_outline Data from PMC are received and updated monthly. - - Citations open_in_new 0 open_in_new 0 open_in_new SEE MORE DETAILS CITE how to cite this article Chen X, Totty M and Hicks S. Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.12688/f1000research.175162.1 ) NOTE: If applicable, it is important to ensure the information in square brackets after the title is included in all citations of this article. COPY CITATION DETAILS track receive updates on this article Track an article to receive email alerts on any updates to this article. TRACK THIS ARTICLE Share Open Peer Review Current Reviewer Status: ? Key to Reviewer Statuses VIEW HIDE Approved The paper is scientifically sound in its current form and only minor, if any, improvements are suggested Approved with reservations A number of small changes, sometimes more significant revisions are required to address specific details and improve the papers academic merit. Not approved Fundamental flaws in the paper seriously undermine the findings and conclusions Version 1 VERSION 1 PUBLISHED 09 Jan 2026 Views 0 Cite How to cite this report: Subramanian A and Zuchowski A. Reviewer Report For: Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.5256/f1000research.193123.r461147 ) The direct URL for this report is: https://f1000research.com/articles/15-33/v1#referee-response-461147 NOTE: it is important to ensure the information in square brackets after the title is included in this citation. Close Copy Citation Details Reviewer Report 13 Mar 2026 Ayshwarya Subramanian , Cornell University, Ithaca, new york, USA Adam Zuchowski , Biomedical Engineering, Cornell University College of Engineering (Ringgold ID: 70235), Ithaca, New York, USA Approved with Reservations VIEWS 0 https://doi.org/10.5256/f1000research.193123.r461147 Summary: This manuscript presents SpotSweeper-py, a Python implementation of the SpotSweeper R package (PMID: 40481362 ), a tool designed for spatially aware quality control of spatial transcriptomics data. By integrating spatial QC functionality within the Python scverse ecosystem widely used for ... Continue reading READ ALL Summary: This manuscript presents SpotSweeper-py, a Python implementation of the SpotSweeper R package (PMID: 40481362 ), a tool designed for spatially aware quality control of spatial transcriptomics data. By integrating spatial QC functionality within the Python scverse ecosystem widely used for single-cell omics analysis, the work fills a clear methodological gap for users working in Python-based workflows. The manuscript demonstrates the utility and scalability of SpotSweeper-py through applications to widely used spatial transcriptomics platforms, including Visium and VisiumHD. The software implementation is well documented, and the code is openly available. The package is user-friendly, with clear installation instructions and example usage that should facilitate adoption by the community. However, because the primary contribution of this manuscript is the reimplementation of an existing method from R into Python, Sections 2.1-2.3 would benefit from a more direct comparison with the original SpotSweeper R package. The current presentation largely reiterates the methodological contributions of the original paper rather than emphasizing what is new in the Python implementation. Benchmarking the two tools in terms of runtime, computational efficiency, and similarity of results would strengthen the manuscript. In addition, claims regarding the biological relevance and spatial context of detected local outliers would benefit from clearer definitions and supporting evidence. Major Concerns: 1. Claims regarding the biological and spatial context of local outliers The manuscript states that local outliers identified by SpotSweeper-py are “biologically meaningful” and “spatially contextualized” (page 5). However, these terms are not clearly defined within the manuscript, and no direct metrics or empirical evidence are presented to support these claims. Suggestion: The authors could address this in 2 ways: explicitly cite and defer to the relevant analyses from the original SpotSweeper publication, clarifying that the evidence supporting these claims originates from prior work. OR provide a brief demonstration within the current manuscript, similar to Figure 2a-d in the original paper, showing that local thresholding preserves biologically meaningful spatial spots that would otherwise be removed by global QC thresholds. 2. Side-by-side comparison and benchmarking against SpotSweeper R Because the goal of this work is to bring the functionality of SpotSweeper to the Python/scverse ecosystem, benchmarking against the existing R implementation would significantly strengthen the manuscript. Specifically, it would be helpful to know: - Whether the Python and R implementations produce consistent results on the same datasets. - How the tools compare in terms of runtime and computational resource usage. -Whether the Python implementation introduces improvements in scalability or integration with Python-based analysis workflows. Suggestion: The original SpotSweeper paper reports a processing rate of approximately 1 second per 1,000 spots per metric. Including a comparable metric for SpotSweeper-py would provide a useful point of reference. Additionally, demonstrating that both implementations identify the same or highly similar sets of local outliers when applied to identical input datasets would strengthen confidence in the Python implementation. Minor Concerns: (1) The manuscript uses a specific value of k when computing the median absolute deviation across neighboring spots. The rationale for this choice is not discussed. Providing guidance on how to select k for different spatial transcriptomics datasets or modalities would improve the practical usability of the method. (2) Section 4.1.4 introduces the constant 0.6745 in the calculation of the local z-score, but its origin is not explained. This constant corresponds to the inverse of the 75th percentile of the standard normal distribution and is used to scale the MAD to approximate the standard deviation. A brief explanation would help readers unfamiliar with robust statistics. (3) The violin plots shown in Figures 3, 7, and 9 contain dashed lines that are not described in the figure captions. The captions should clarify whether these lines represent the mean, median, or another summary statistic. (4) The comparison between global and local QC focuses on outliers uniquely identified by each approach. However, the manuscript does not discuss spots identified as outliers by both methods. Characterizing this shared set would provide useful context for understanding the degree of agreement between the approaches and could help clarify what additional information is provided by the local QC strategy. (5) Line 5 contains a likely typo: “inside” may have been intended to read “insight.” Is the rationale for developing the new software tool clearly explained? Yes Is the description of the software tool technically sound? Yes Are sufficient details of the code, methods and analysis (if applicable) provided to allow replication of the software development and its use by others? Yes Is sufficient information provided to allow interpretation of the expected output datasets and any results generated using the tool? Yes Are the conclusions about the tool and its performance adequately supported by the findings presented in the article? Partly Competing Interests: No competing interests were disclosed. Reviewer Expertise: single-cell technologies, computational biology We confirm that we have read this submission and believe that we have an appropriate level of expertise to confirm that it is of an acceptable scientific standard, however we have significant reservations, as outlined above. Close READ LESS CITE CITE HOW TO CITE THIS REPORT Subramanian A and Zuchowski A. Reviewer Report For: Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.5256/f1000research.193123.r461147 ) The direct URL for this report is: https://f1000research.com/articles/15-33/v1#referee-response-461147 NOTE: it is important to ensure the information in square brackets after the title is included in all citations of this article. COPY CITATION DETAILS Report a concern Respond or Comment COMMENT ON THIS REPORT Views 0 Cite How to cite this report: Kamau Njage PM. Reviewer Report For: Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.5256/f1000research.193123.r455887 ) The direct URL for this report is: https://f1000research.com/articles/15-33/v1#referee-response-455887 NOTE: it is important to ensure the information in square brackets after the title is included in this citation. Close Copy Citation Details Reviewer Report 24 Feb 2026 Patrick Murigu Kamau Njage , Technical University of Denmark, Lyngby, Denmark Approved with Reservations VIEWS 0 https://doi.org/10.5256/f1000research.193123.r455887 Page 3: revise the sentence “Local outliers are then flagged one or two sided based on user-defined cutoffs.” Page 3, Section 2.1 line 3: UMI- define this abbreviation at place of first mention in the manuscript. Page ... Continue reading READ ALL Page 3: revise the sentence “Local outliers are then flagged one or two sided based on user-defined cutoffs.” Page 3, Section 2.1 line 3: UMI- define this abbreviation at place of first mention in the manuscript. Page 4, line 2, last line: it is not clear how ground-truthing was performed to conclude that discarded regions were biologically meaningful. Biological meaning can only be inferred based on a validated comparison between the discarded regions and those from e.g. histological examination or other biological characterisation. Page 9: “This separation demonstrates that local QC captures spatially aware deviations that define technical artifacts at a high resolution, whereas global QC merely flags significant global shifts.”- this inference may be erroneous concerning what consists of technical artifacts or not. Basing this distinction on z-score distribution may lead to falsely flagging as an artifact of true but exceptional biologically meaningful data. Page 10 “ SpotSweeper-py reduced false positives…”- related to above, it is not clear if a golden standard was used to verify the results and conclude about false positives. Page 10: “Here, we demonstrated how to control bias in outlier detection…” it is not clear if bias metrics were used to clearly show that bias was controlled. Page 10: “The core function to detect local outliers, operates on a single anndata. AnnData object and expects the following components to be available in the AnnData object.” It seems like the repeat of anndata and split of the sentence was not intended. Section 4.1.3: This section quotes k=36 as a default. The study, however, uses other values such as k=48 in section 2.3. This raises the question if the results of the study are not influenced and only specific for the by values of k used. How about other values of k such as 20, 40 etc? Below is a structured reviewer report for the manuscript: Chen X, Totty M, Hicks S. Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem Reviewer Report General Assessment This manuscript presents SpotSweeper-py , a Python implementation of the previously published SpotSweeper framework for spatially-aware quality control (QC) in spatial transcriptomics data . The work addresses a meaningful gap by translating robust local QC methods into the Python/scverse ecosystem, where spatial transcriptomics workflows are increasingly standardized. The manuscript is clearly structured, and the use cases on Visium and VisiumHD datasets effectively demonstrate computational feasibility and practical integration with AnnData workflows. The implementation appears well engineered, reproducible, and documented. The methodological sections are technically sound and transparent. However, several interpretive statements require clarification or more cautious framing, particularly regarding claims about “false positives,” “technical artifacts,” and “bias control.” Some wording and definitional clarifications are also needed. Overall, the work is valuable and likely to be impactful after revision. Major Comments 1. Interpretation of Local QC as Defining Technical Artifacts (Page 9) The statement: “This separation demonstrates that local QC captures spatially aware deviations that define technical artifacts at a high resolution…” (Page 9) is interpretatively strong and potentially overstated. While the clear separation of local z-score distributions (Figure 9B) supports that the method detects spatially localized deviations, it does not establish that these deviations correspond to technical artifacts . Exceptional or rare biological phenomena (e.g., necrotic niches, metabolic hotspots, tumor microenvironment gradients) may also appear as neighborhood-level deviations. Recommendation: Rephrase to reflect statistical detection of spatial deviations rather than definitive identification of technical artifacts. In addition, discuss more explicitly that confirmation requires orthogonal biological validation. 2. False Positive Claims Without a Gold Standard (Page 10) The manuscript states: “SpotSweeper-py reduced false positives…” (Page 10) However, no gold-standard annotations (e.g., histological validation, pathologist labeling, imaging-based assessment, or spike-in controls) are presented to define what constitutes a false positive. Reduced flagging relative to global thresholds does not necessarily imply fewer false positives — it may instead reflect reduced sensitivity. The manuscript currently conflates reduced flagging rate with error reduction. Recommendation: Either: Provide evidence of external validation (histology alignment, manual annotation comparison, biological enrichment tests), or Rephrase to a more cautious interpretation: 3. Ground-Truthing and Biological Meaning (Page 4) The manuscript suggests that globally discarded regions are “likely biologically meaningful” (Page 4) , but it is unclear how this conclusion was established. Biological meaningfulness cannot be inferred without validated comparison, such as: Histological overlays Marker gene expression consistency Pathologist annotations Morphological segmentation Comparative QC with orthogonal spatial technologies Recommendation: Clarify what constitutes “biologically meaningful.” If no external validation is available, soften language accordingly: E.g. “...large contiguous regions that may correspond to intact tissue architecture.” 4. Claim of Bias Control (Page 10) The manuscript states: “Here, we demonstrated how to control bias in outlier detection…” However, no explicit bias metrics are defined, quantified, or benchmarked. While the use of medians, MAD, and log transformations reduces influence of extreme values, this represents robustness, not explicitly measured bias control. Recommendation: Rephrase to: “Here, we demonstrate how robust and neighborhood-aware statistics can mitigate the influence of extreme values in outlier detection.” Alternatively, quantify bias if formally evaluated. 5. Sensitivity to k (Neighborhood Size) Section 4.1.3 states that the default is k = 36 , while Sections 2.2–2.3 use k = 48 for VisiumHD datasets. No sensitivity analysis is presented to evaluate robustness of findings across k ∈ {20, 30, 40, 48, etc.}. Since neighborhood size directly controls bias–variance tradeoff: Smaller k increases variance and detection sensitivity Larger k increases smoothing and may mask local anomalies Recommendation: Include: Sensitivity analysis for several k values Plot showing proportion flagged vs k Discussion of stability of spatial patterns across k Even a brief supplementary analysis would strengthen reproducibility and generalizability. Minor Comments 1. Sentence Clarification (Page 3) Original: “Local outliers are then flagged one or two sided based on user-defined cutoffs.” Suggested revision: “Local outliers are then flagged as one-sided or two-sided based on user-defined z-score cutoffs.” 2. Define UMI (Page 3, Section 2.1) “UMI” should be defined at first use as: Unique Molecular Identifier (UMI) 3. Typographical / Sentence Fragment (Page 10) Original: “The core function to detect local outliers, operates on a single anndata. AnnData object…” Correct to: “The core function to detect local outliers operates on a single AnnData object…” 4. Clarify “mis-flagged” Language (Page 6; Figure 4) The manuscript states that spots are “mis-flagged” by global QC . Unless validated, this should be softened to: “flagged by global QC but not by local QC” Minor comments Explicit Limitations Section The discussion should more clearly acknowledge: No gold standard labeling Assumption of uniform spatial density Possible oversensitivity in edge regions Sensitivity to anisotropic tissue structure Compare with Alternative Methods Given references to other QC tools (e.g., SpatialQC, Sprod), a short comparison table would strengthen positioning if possible. Computational Complexity Characterization Runtime scaling is described narratively; including O(n log n) or similar computational complexity analysis would improve technical clarity. Is the rationale for developing the new software tool clearly explained? Yes Is the description of the software tool technically sound? Yes Are sufficient details of the code, methods and analysis (if applicable) provided to allow replication of the software development and its use by others? Yes Is sufficient information provided to allow interpretation of the expected output datasets and any results generated using the tool? Yes Are the conclusions about the tool and its performance adequately supported by the findings presented in the article? Partly Competing Interests: No competing interests were disclosed. Reviewer Expertise: Genomic epidemiology. Use of data science and statistics for infectious disease modelling. I confirm that I have read this submission and believe that I have an appropriate level of expertise to confirm that it is of an acceptable scientific standard, however I have significant reservations, as outlined above. Close READ LESS CITE CITE HOW TO CITE THIS REPORT Kamau Njage PM. Reviewer Report For: Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.5256/f1000research.193123.r455887 ) The direct URL for this report is: https://f1000research.com/articles/15-33/v1#referee-response-455887 NOTE: it is important to ensure the information in square brackets after the title is included in all citations of this article. COPY CITATION DETAILS Report a concern Respond or Comment COMMENT ON THIS REPORT Comments on this article Comments (0) Version 1 VERSION 1 PUBLISHED 09 Jan 2026 ADD YOUR COMMENT Comment keyboard_arrow_left keyboard_arrow_right Open Peer Review Reviewer Status info_outline Alongside their report, reviewers assign a status to the article: Approved The paper is scientifically sound in its current form and only minor, if any, improvements are suggested Approved with reservations A number of small changes, sometimes more significant revisions are required to address specific details and improve the papers academic merit. Not approved Fundamental flaws in the paper seriously undermine the findings and conclusions Reviewer Reports Invited Reviewers 1 2 Version 1 09 Jan 26 read read Patrick Murigu Kamau Njage , Technical University of Denmark, Lyngby, Denmark Ayshwarya Subramanian , Cornell University, Ithaca, USA Adam Zuchowski , Cornell University College of Engineering (Ringgold ID: 70235), Ithaca, USA Comments on this article All Comments (0) Add a comment Sign up for content alerts Sign Up You are now signed up to receive this alert Browse by related subjects keyboard_arrow_left Back to all reports Reviewer Report 0 Views copyright © 2026 Subramanian A et al. This is an open access peer review report distributed under the terms of the Creative Commons Attribution License , which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. 13 Mar 2026 | for Version 1 Ayshwarya Subramanian , Cornell University, Ithaca, new york, USA Adam Zuchowski , Biomedical Engineering, Cornell University College of Engineering (Ringgold ID: 70235), Ithaca, New York, USA 0 Views copyright © 2026 Subramanian A et al. This is an open access peer review report distributed under the terms of the Creative Commons Attribution License , which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. format_quote Cite this report speaker_notes Responses (0) Approved With Reservations info_outline Alongside their report, reviewers assign a status to the article: Approved The paper is scientifically sound in its current form and only minor, if any, improvements are suggested Approved with reservations A number of small changes, sometimes more significant revisions are required to address specific details and improve the papers academic merit. Not approved Fundamental flaws in the paper seriously undermine the findings and conclusions Summary: This manuscript presents SpotSweeper-py, a Python implementation of the SpotSweeper R package (PMID: 40481362 ), a tool designed for spatially aware quality control of spatial transcriptomics data. By integrating spatial QC functionality within the Python scverse ecosystem widely used for single-cell omics analysis, the work fills a clear methodological gap for users working in Python-based workflows. The manuscript demonstrates the utility and scalability of SpotSweeper-py through applications to widely used spatial transcriptomics platforms, including Visium and VisiumHD. The software implementation is well documented, and the code is openly available. The package is user-friendly, with clear installation instructions and example usage that should facilitate adoption by the community. However, because the primary contribution of this manuscript is the reimplementation of an existing method from R into Python, Sections 2.1-2.3 would benefit from a more direct comparison with the original SpotSweeper R package. The current presentation largely reiterates the methodological contributions of the original paper rather than emphasizing what is new in the Python implementation. Benchmarking the two tools in terms of runtime, computational efficiency, and similarity of results would strengthen the manuscript. In addition, claims regarding the biological relevance and spatial context of detected local outliers would benefit from clearer definitions and supporting evidence. Major Concerns: 1. Claims regarding the biological and spatial context of local outliers The manuscript states that local outliers identified by SpotSweeper-py are “biologically meaningful” and “spatially contextualized” (page 5). However, these terms are not clearly defined within the manuscript, and no direct metrics or empirical evidence are presented to support these claims. Suggestion: The authors could address this in 2 ways: explicitly cite and defer to the relevant analyses from the original SpotSweeper publication, clarifying that the evidence supporting these claims originates from prior work. OR provide a brief demonstration within the current manuscript, similar to Figure 2a-d in the original paper, showing that local thresholding preserves biologically meaningful spatial spots that would otherwise be removed by global QC thresholds. 2. Side-by-side comparison and benchmarking against SpotSweeper R Because the goal of this work is to bring the functionality of SpotSweeper to the Python/scverse ecosystem, benchmarking against the existing R implementation would significantly strengthen the manuscript. Specifically, it would be helpful to know: - Whether the Python and R implementations produce consistent results on the same datasets. - How the tools compare in terms of runtime and computational resource usage. -Whether the Python implementation introduces improvements in scalability or integration with Python-based analysis workflows. Suggestion: The original SpotSweeper paper reports a processing rate of approximately 1 second per 1,000 spots per metric. Including a comparable metric for SpotSweeper-py would provide a useful point of reference. Additionally, demonstrating that both implementations identify the same or highly similar sets of local outliers when applied to identical input datasets would strengthen confidence in the Python implementation. Minor Concerns: (1) The manuscript uses a specific value of k when computing the median absolute deviation across neighboring spots. The rationale for this choice is not discussed. Providing guidance on how to select k for different spatial transcriptomics datasets or modalities would improve the practical usability of the method. (2) Section 4.1.4 introduces the constant 0.6745 in the calculation of the local z-score, but its origin is not explained. This constant corresponds to the inverse of the 75th percentile of the standard normal distribution and is used to scale the MAD to approximate the standard deviation. A brief explanation would help readers unfamiliar with robust statistics. (3) The violin plots shown in Figures 3, 7, and 9 contain dashed lines that are not described in the figure captions. The captions should clarify whether these lines represent the mean, median, or another summary statistic. (4) The comparison between global and local QC focuses on outliers uniquely identified by each approach. However, the manuscript does not discuss spots identified as outliers by both methods. Characterizing this shared set would provide useful context for understanding the degree of agreement between the approaches and could help clarify what additional information is provided by the local QC strategy. (5) Line 5 contains a likely typo: “inside” may have been intended to read “insight.” Is the rationale for developing the new software tool clearly explained? Yes Is the description of the software tool technically sound? Yes Are sufficient details of the code, methods and analysis (if applicable) provided to allow replication of the software development and its use by others? Yes Is sufficient information provided to allow interpretation of the expected output datasets and any results generated using the tool? Yes Are the conclusions about the tool and its performance adequately supported by the findings presented in the article? Partly Competing Interests No competing interests were disclosed. Reviewer Expertise single-cell technologies, computational biology We confirm that we have read this submission and believe that we have an appropriate level of expertise to confirm that it is of an acceptable scientific standard, however we have significant reservations, as outlined above. reply Respond to this report Responses (0) Subramanian A and Zuchowski A. Peer Review Report For: Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.5256/f1000research.193123.r461147) NOTE: it is important to ensure the information in square brackets after the title is included in this citation. The direct URL for this report is: https://f1000research.com/articles/15-33/v1#referee-response-461147 keyboard_arrow_left Back to all reports Reviewer Report 0 Views copyright © 2026 Kamau Njage P. This is an open access peer review report distributed under the terms of the Creative Commons Attribution License , which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. 24 Feb 2026 | for Version 1 Patrick Murigu Kamau Njage , Technical University of Denmark, Lyngby, Denmark 0 Views copyright © 2026 Kamau Njage P. This is an open access peer review report distributed under the terms of the Creative Commons Attribution License , which permits unrestricted use, distribution, and reproduction in any medium, provided the original work is properly cited. format_quote Cite this report speaker_notes Responses (0) Approved With Reservations info_outline Alongside their report, reviewers assign a status to the article: Approved The paper is scientifically sound in its current form and only minor, if any, improvements are suggested Approved with reservations A number of small changes, sometimes more significant revisions are required to address specific details and improve the papers academic merit. Not approved Fundamental flaws in the paper seriously undermine the findings and conclusions Page 3: revise the sentence “Local outliers are then flagged one or two sided based on user-defined cutoffs.” Page 3, Section 2.1 line 3: UMI- define this abbreviation at place of first mention in the manuscript. Page 4, line 2, last line: it is not clear how ground-truthing was performed to conclude that discarded regions were biologically meaningful. Biological meaning can only be inferred based on a validated comparison between the discarded regions and those from e.g. histological examination or other biological characterisation. Page 9: “This separation demonstrates that local QC captures spatially aware deviations that define technical artifacts at a high resolution, whereas global QC merely flags significant global shifts.”- this inference may be erroneous concerning what consists of technical artifacts or not. Basing this distinction on z-score distribution may lead to falsely flagging as an artifact of true but exceptional biologically meaningful data. Page 10 “ SpotSweeper-py reduced false positives…”- related to above, it is not clear if a golden standard was used to verify the results and conclude about false positives. Page 10: “Here, we demonstrated how to control bias in outlier detection…” it is not clear if bias metrics were used to clearly show that bias was controlled. Page 10: “The core function to detect local outliers, operates on a single anndata. AnnData object and expects the following components to be available in the AnnData object.” It seems like the repeat of anndata and split of the sentence was not intended. Section 4.1.3: This section quotes k=36 as a default. The study, however, uses other values such as k=48 in section 2.3. This raises the question if the results of the study are not influenced and only specific for the by values of k used. How about other values of k such as 20, 40 etc? Below is a structured reviewer report for the manuscript: Chen X, Totty M, Hicks S. Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem Reviewer Report General Assessment This manuscript presents SpotSweeper-py , a Python implementation of the previously published SpotSweeper framework for spatially-aware quality control (QC) in spatial transcriptomics data . The work addresses a meaningful gap by translating robust local QC methods into the Python/scverse ecosystem, where spatial transcriptomics workflows are increasingly standardized. The manuscript is clearly structured, and the use cases on Visium and VisiumHD datasets effectively demonstrate computational feasibility and practical integration with AnnData workflows. The implementation appears well engineered, reproducible, and documented. The methodological sections are technically sound and transparent. However, several interpretive statements require clarification or more cautious framing, particularly regarding claims about “false positives,” “technical artifacts,” and “bias control.” Some wording and definitional clarifications are also needed. Overall, the work is valuable and likely to be impactful after revision. Major Comments 1. Interpretation of Local QC as Defining Technical Artifacts (Page 9) The statement: “This separation demonstrates that local QC captures spatially aware deviations that define technical artifacts at a high resolution…” (Page 9) is interpretatively strong and potentially overstated. While the clear separation of local z-score distributions (Figure 9B) supports that the method detects spatially localized deviations, it does not establish that these deviations correspond to technical artifacts . Exceptional or rare biological phenomena (e.g., necrotic niches, metabolic hotspots, tumor microenvironment gradients) may also appear as neighborhood-level deviations. Recommendation: Rephrase to reflect statistical detection of spatial deviations rather than definitive identification of technical artifacts. In addition, discuss more explicitly that confirmation requires orthogonal biological validation. 2. False Positive Claims Without a Gold Standard (Page 10) The manuscript states: “SpotSweeper-py reduced false positives…” (Page 10) However, no gold-standard annotations (e.g., histological validation, pathologist labeling, imaging-based assessment, or spike-in controls) are presented to define what constitutes a false positive. Reduced flagging relative to global thresholds does not necessarily imply fewer false positives — it may instead reflect reduced sensitivity. The manuscript currently conflates reduced flagging rate with error reduction. Recommendation: Either: Provide evidence of external validation (histology alignment, manual annotation comparison, biological enrichment tests), or Rephrase to a more cautious interpretation: 3. Ground-Truthing and Biological Meaning (Page 4) The manuscript suggests that globally discarded regions are “likely biologically meaningful” (Page 4) , but it is unclear how this conclusion was established. Biological meaningfulness cannot be inferred without validated comparison, such as: Histological overlays Marker gene expression consistency Pathologist annotations Morphological segmentation Comparative QC with orthogonal spatial technologies Recommendation: Clarify what constitutes “biologically meaningful.” If no external validation is available, soften language accordingly: E.g. “...large contiguous regions that may correspond to intact tissue architecture.” 4. Claim of Bias Control (Page 10) The manuscript states: “Here, we demonstrated how to control bias in outlier detection…” However, no explicit bias metrics are defined, quantified, or benchmarked. While the use of medians, MAD, and log transformations reduces influence of extreme values, this represents robustness, not explicitly measured bias control. Recommendation: Rephrase to: “Here, we demonstrate how robust and neighborhood-aware statistics can mitigate the influence of extreme values in outlier detection.” Alternatively, quantify bias if formally evaluated. 5. Sensitivity to k (Neighborhood Size) Section 4.1.3 states that the default is k = 36 , while Sections 2.2–2.3 use k = 48 for VisiumHD datasets. No sensitivity analysis is presented to evaluate robustness of findings across k ∈ {20, 30, 40, 48, etc.}. Since neighborhood size directly controls bias–variance tradeoff: Smaller k increases variance and detection sensitivity Larger k increases smoothing and may mask local anomalies Recommendation: Include: Sensitivity analysis for several k values Plot showing proportion flagged vs k Discussion of stability of spatial patterns across k Even a brief supplementary analysis would strengthen reproducibility and generalizability. Minor Comments 1. Sentence Clarification (Page 3) Original: “Local outliers are then flagged one or two sided based on user-defined cutoffs.” Suggested revision: “Local outliers are then flagged as one-sided or two-sided based on user-defined z-score cutoffs.” 2. Define UMI (Page 3, Section 2.1) “UMI” should be defined at first use as: Unique Molecular Identifier (UMI) 3. Typographical / Sentence Fragment (Page 10) Original: “The core function to detect local outliers, operates on a single anndata. AnnData object…” Correct to: “The core function to detect local outliers operates on a single AnnData object…” 4. Clarify “mis-flagged” Language (Page 6; Figure 4) The manuscript states that spots are “mis-flagged” by global QC . Unless validated, this should be softened to: “flagged by global QC but not by local QC” Minor comments Explicit Limitations Section The discussion should more clearly acknowledge: No gold standard labeling Assumption of uniform spatial density Possible oversensitivity in edge regions Sensitivity to anisotropic tissue structure Compare with Alternative Methods Given references to other QC tools (e.g., SpatialQC, Sprod), a short comparison table would strengthen positioning if possible. Computational Complexity Characterization Runtime scaling is described narratively; including O(n log n) or similar computational complexity analysis would improve technical clarity. Is the rationale for developing the new software tool clearly explained? Yes Is the description of the software tool technically sound? Yes Are sufficient details of the code, methods and analysis (if applicable) provided to allow replication of the software development and its use by others? Yes Is sufficient information provided to allow interpretation of the expected output datasets and any results generated using the tool? Yes Are the conclusions about the tool and its performance adequately supported by the findings presented in the article? Partly Competing Interests No competing interests were disclosed. Reviewer Expertise Genomic epidemiology. Use of data science and statistics for infectious disease modelling. I confirm that I have read this submission and believe that I have an appropriate level of expertise to confirm that it is of an acceptable scientific standard, however I have significant reservations, as outlined above. reply Respond to this report Responses (0) Kamau Njage PM. Peer Review Report For: Spotsweeper-py: spatially-aware quality control metrics for spatial omics data in the Python ecosystem [version 1; peer review: 2 approved with reservations] . F1000Research 2026, 15 :33 ( https://doi.org/10.5256/f1000research.193123.r455887) NOTE: it is important to ensure the information in square brackets after the title is included in this citation. The direct URL for this report is: https://f1000research.com/articles/15-33/v1#referee-response-455887 Alongside their report, reviewers assign a status to the article: Approved - the paper is scientifically sound in its current form and only minor, if any, improvements are suggested Approved with reservations - A number of small changes, sometimes more significant revisions are required to address specific details and improve the papers academic merit. Not approved - fundamental flaws in the paper seriously undermine the findings and conclusions Adjust parameters to alter display View on desktop for interactive features Includes Interactive Elements View on desktop for interactive features Competing Interests Policy Provide sufficient details of any financial or non-financial competing interests to enable users to assess whether your comments might lead a reasonable person to question your impartiality. Consider the following examples, but note that this is not an exhaustive list: Examples of 'Non-Financial Competing Interests' Within the past 4 years, you have held joint grants, published or collaborated with any of the authors of the selected paper. You have a close personal relationship (e.g. parent, spouse, sibling, or domestic partner) with any of the authors. You are a close professional associate of any of the authors (e.g. scientific mentor, recent student). You work at the same institute as any of the authors. You hope/expect to benefit (e.g. favour or employment) as a result of your submission. You are an Editor for the journal in which the article is published. Examples of 'Financial Competing Interests' You expect to receive, or in the past 4 years have received, any of the following from any commercial organisation that may gain financially from your submission: a salary, fees, funding, reimbursements. You expect to receive, or in the past 4 years have received, shared grant support or other funding with any of the authors. You hold, or are currently applying for, any patents or significant stocks/shares relating to the subject matter of the paper you are commenting on. Stay Updated Sign up for content alerts and receive a weekly or monthly email with all newly published articles Register with F1000Research Already registered? Sign in Not now, thanks close PLEASE NOTE If you are an AUTHOR of this article, please check that you signed in with the account associated with this article otherwise we cannot automatically identify your role as an author and your comment will be labelled as a “User Comment”. If you are a REVIEWER of this article, please check that you have signed in with the account associated with this article and then go to your account to submit your report, please do not post your review here. If you do not have access to your original account, please contact us . All commenters must hold a formal affiliation as per our Policies . The information that you give us will be displayed next to your comment. User comments must be in English, comprehensible and relevant to the article under discussion. We reserve the right to remove any comments that we consider to be inappropriate, offensive or otherwise in breach of the User Comment Terms and Conditions . Commenters must not use a comment for personal attacks. When criticisms of the article are based on unpublished data, the data should be made available. I accept the User Comment Terms and Conditions Please confirm that you accept the User Comment Terms and Conditions. Affiliation ✕ refresh Please enter your institution. Note: To add your institution or organisation, start typing the name and then select the correct name from the list. Where applicable, the name will appear in both the original language and in English. Do not paste in the name. If the name does not appear in the drop-down list, we will display the information you have entered. ✕ refresh Country/Region * USA UK Canada China France Germany Afghanistan Aland Islands Albania Algeria American Samoa Andorra Angola Anguilla Antarctica Antigua and Barbuda Argentina Armenia Aruba Australia Austria Azerbaijan Bahamas Bahrain Bangladesh Barbados Belarus Belgium Belize Benin Bermuda Bhutan Bolivia Bosnia and Herzegovina Botswana Bouvet Island Brazil British Indian Ocean Territory British Virgin Islands Brunei Bulgaria Burkina Faso Burundi Cambodia Cameroon Canada Cape Verde Cayman Islands Central African Republic Chad Chile China Christmas Island Cocos (Keeling) Islands Colombia Comoros Congo Cook Islands Costa Rica Cote d'Ivoire Croatia Cuba Cyprus Czech Republic Democratic Republic of the Congo Denmark Djibouti Dominica Dominican Republic Ecuador Egypt El Salvador Equatorial Guinea Eritrea Estonia Ethiopia Falkland Islands Faroe Islands Federated States of Micronesia Fiji Finland France French Guiana French Polynesia French Southern Territories Gabon Georgia Germany Ghana Gibraltar Greece Greenland Grenada Guadeloupe Guam Guatemala Guernsey Guinea Guinea-Bissau Guyana Haiti Heard Island and Mcdonald Islands Holy See (Vatican City State) Honduras Hong Kong Hungary Iceland India Indonesia Iran Iraq Ireland Israel Italy Jamaica Japan Jersey Jordan Kazakhstan Kenya Kiribati Kosovo (Serbia and Montenegro) Kuwait Kyrgyzstan Lao People's Democratic Republic Latvia Lebanon Lesotho Liberia Libya Liechtenstein Lithuania Luxembourg Macao Madagascar Malawi Malaysia Maldives Mali Malta Marshall Islands Martinique Mauritania Mauritius Mayotte Mexico Minor Outlying Islands of the United States Moldova Monaco Mongolia Montenegro Montserrat Morocco Mozambique Myanmar Namibia Nauru Nepal Netherlands Antilles New Caledonia New Zealand Nicaragua Niger Nigeria Niue Norfolk Island North Korea North Macedonia Northern Mariana Islands Norway Oman Pakistan Palau Palestinian Territory Panama Papua New Guinea Paraguay Peru Philippines Pitcairn Poland Portugal Puerto Rico Qatar Reunion Romania Russian Federation Rwanda Saint Helena Saint Kitts and Nevis Saint Lucia Saint Pierre and Miquelon Saint Vincent and the Grenadines Samoa San Marino Sao Tome and Principe Saudi Arabia Senegal Serbia Seychelles Sierra Leone Singapore Slovakia Slovenia Solomon Islands Somalia South Africa South Georgia and the South Sandwich Is South Korea South Sudan Spain Sri Lanka Sudan Suriname Svalbard and Jan Mayen Swaziland Sweden Switzerland Syria Taiwan Tajikistan Tanzania Thailand The Gambia The Netherlands Timor-Leste Togo Tokelau Tonga Trinidad and Tobago Tunisia Turkey Turkmenistan Turks and Caicos Islands Tuvalu UK USA Uganda Ukraine United Arab Emirates United States Virgin Islands Uruguay Uzbekistan Vanuatu Venezuela Vietnam Wallis and Futuna West Bank and Gaza Strip Western Sahara Yemen Zambia Zimbabwe Please select your country/region. You must enter a comment. Competing Interests Please disclose any competing interests that might be construed to influence your judgment of the article's or peer review report's validity or importance. Competing Interests Policy Provide sufficient details of any financial or non-financial competing interests to enable users to assess whether your comments might lead a reasonable person to question your impartiality. Consider the following examples, but note that this is not an exhaustive list: Examples of 'Non-Financial Competing Interests' Within the past 4 years, you have held joint grants, published or collaborated with any of the authors of the selected paper. You have a close personal relationship (e.g. parent, spouse, sibling, or domestic partner) with any of the authors. You are a close professional associate of any of the authors (e.g. scientific mentor, recent student). You work at the same institute as any of the authors. You hope/expect to benefit (e.g. favour or employment) as a result of your submission. You are an Editor for the journal in which the article is published. Examples of 'Financial Competing Interests' You expect to receive, or in the past 4 years have received, any of the following from any commercial organisation that may gain financially from your submission: a salary, fees, funding, reimbursements. You expect to receive, or in the past 4 years have received, shared grant support or other funding with any of the authors. You hold, or are currently applying for, any patents or significant stocks/shares relating to the subject matter of the paper you are commenting on. Please state your competing interests The comment has been saved. An error has occurred. Please try again. Cancel Post var lTitle = "Spotsweeper-py: spatially-aware quality control...".replace("'", ''); var linkedInUrl = "http://www.linkedin.com/shareArticle?url=https://f1000research.com/articles/15-33/v1" + "&title=" + encodeURIComponent(lTitle) + "&summary=" + encodeURIComponent('Read the article by '); var deliciousUrl = "https://del.icio.us/post?url=https://f1000research.com/articles/15-33/v1&title=" + encodeURIComponent(lTitle); var redditUrl = "http://reddit.com/submit?url=https://f1000research.com/articles/15-33/v1" + "&title=" + encodeURIComponent(lTitle); linkedInUrl += encodeURIComponent('Chen X et al.'); var offsetTop = /chrome/i.test( navigator.userAgent ) ? 4 : -10; var addthis_config = { ui_offset_top: offsetTop, services_compact : "facebook,twitter,www.linkedin.com,www.mendeley.com,reddit.com", services_expanded : "facebook,twitter,www.linkedin.com,www.mendeley.com,reddit.com", services_custom : [ { name: "LinkedIn", url: linkedInUrl, icon:"/img/icon/at_linkedin.svg" }, { name: "Mendeley", url: "http://www.mendeley.com/import/?url=https://f1000research.com/articles/15-33/v1/mendeley", icon:"/img/icon/at_mendeley.svg" }, { name: "Reddit", url: redditUrl, icon:"/img/icon/at_reddit.svg" }, ] }; var addthis_share = { url: "https://f1000research.com/articles/15-33", templates : { twitter : "Spotsweeper-py: spatially-aware quality control metrics for spatial.... Chen X et al., published by " + "@F1000Research" + ", https://f1000research.com/articles/15-33/v1" } }; if (typeof(addthis) != "undefined"){ addthis.addEventListener('addthis.ready', checkCount); addthis.addEventListener('addthis.menu.share', checkCount); } $(".f1r-shares-twitter").attr("href", "https://twitter.com/intent/tweet?text=" + addthis_share.templates.twitter); $(".f1r-shares-facebook").attr("href", "https://www.facebook.com/sharer/sharer.php?u=" + addthis_share.url); $(".f1r-shares-linkedin").attr("href", addthis_config.services_custom[0].url); $(".f1r-shares-reddit").attr("href", addthis_config.services_custom[2].url); $(".f1r-shares-mendelay").attr("href", addthis_config.services_custom[1].url); function checkCount(){ setTimeout(function(){ $(".addthis_button_expanded").each(function(){ var count = $(this).text(); if (count !== "" && count != "0") $(this).removeClass("is-hidden"); else $(this).addClass("is-hidden"); }); }, 1000); } close How to cite this report {{reportCitation}} Cancel Copy Citation Details $(function(){R.ui.buttonDropdowns('.dropdown-for-downloads');}); $(function(){R.ui.toolbarDropdowns('.toolbar-dropdown-for-downloads');}); $.get("/articles/acj/175162/193123") new F1000.Clipboard(); new F1000.ThesaurusTermsDisplay("articles", "article", "193123"); $(document).ready(function() { $( "#frame1" ).on('load', function() { var mydiv = $(this).contents().find("div"); var h = mydiv.height(); console.log(h) }); var tooltipLivingFigure = jQuery(".interactive-living-figure-label .icon-more-info"), titleLivingFigure = tooltipLivingFigure.attr("title"); tooltipLivingFigure.simpletip({ fixed: true, position: ["-115", "30"], baseClass: 'small-tooltip', content:titleLivingFigure + " " }); tooltipLivingFigure.removeAttr("title"); $("body").on("click", ".cite-living-figure", function(e) { e.preventDefault(); var ref = $(this).attr("data-ref"); $(this).closest(".living-figure-list-container").find("#" + ref).fadeIn(200); }); $("body").on("click", ".close-cite-living-figure", function(e) { e.preventDefault(); $(this).closest(".popup-window-wrapper").fadeOut(200); }); $(document).on("mouseup", function(e) { var metricsContainer = $(".article-metrics-popover-wrapper"); if (!metricsContainer.is(e.target) && metricsContainer.has(e.target).length === 0) { $(".article-metrics-close-button").click(); } }); var articleId = $('#articleId').val(); if($("#main-article-count-box").attachArticleMetrics) { $("#main-article-count-box").attachArticleMetrics(articleId, { articleMetricsView: true }); } }); var figshareWidget = $(".new_figshare_widget"); if (figshareWidget.length > 0) { window.figshare.load("f1000", function(Widget) { // Select a tag/tags defined in your page. In this tag we will place the widget. _.map(figshareWidget, function(el){ var widget = new Widget({ articleId: $(el).attr("figshare_articleId") //height:300 // this is the height of the viewer part. [Default: 550] }); widget.initialize(); // initialize the widget widget.mount(el); // mount it in a tag that's on your page // this will save the widget on the global scope for later use from // your JS scripts. This line is optional. //window.widget = widget; }); }); } close Error Close Add Reset F1000.MICROSERVICES.AFFILIATION = ''; $(document).ready(function () { $('.js-affiliations-form').each((index, form) => { new AffiliationForm({ formId: form.id, institutionErrorSelector: '.comment-enter-institution', departmentErrorSelector: '.comment-enter-department', placeSelector: '.js-add-comment-place', stateSelector: '.js-add-comment-state', zipCodeSelector: '.js-add-comment-zipcode', countrySelector: '.js-add-comment-country', countryErrorSelector: '.comment-enter-country', }); }); }); $(document).ready(function () { var reportIds = { "449798": 0, "455879": 0, "449799": 0, "455886": 0, "449806": 0, "455887": 15, "449807": 0, "455884": 0, "449804": 0, "455885": 0, "449805": 0, "455882": 0, "449802": 0, "455883": 0, "449803": 0, "455880": 0, "449800": 0, "455881": 0, "449801": 0, "461143": 0, "455888": 0, "455889": 0, "461151": 0, "461150": 0, "461149": 0, "461148": 0, "461147": 14, "461146": 0, "461145": 0, "461144": 0, "461152": 0, "453046": 0, "453047": 0, "453044": 0, "453045": 0, "453042": 0, "453043": 0, "453041": 0, "453050": 0, "453048": 0, "453049": 0, }; $(".referee-response-container,.js-referee-report").each(function(index, el) { var reportId = $(el).attr("data-reportid"), reportCount = reportIds[reportId] || 0; $(el).find(".comments-count-container,.js-referee-report-views").html(reportCount); }); var uuidInput = $("#article_uuid"), oldUUId = uuidInput.val(), newUUId = "d041a442-84e3-4f63-a59d-e01c03d44968"; uuidInput.val(newUUId); $("a[href*='article_uuid=']").each(function(index, el) { var newHref = $(el).attr("href").replace(oldUUId, newUUId); $(el).attr("href", newHref); }); }); An innovative open access publishing platform offering rapid publication and open peer review, whilst supporting data deposition and sharing. Browse Gateways Collections How it Works Contact For Developers Cookie Notice Privacy Notice RSS Submit Your Research Follow us © 2012-2026 F1000 Research Ltd. ISSN 2046-1402 | Legal | Partner of Research4Life • CrossRef • ORCID • FAIRSharing R.templateTests.simpleTemplate = R.template(' $text $text $text $text $text '); R.templateTests.runTests(); var F1000platform = new F1000.Platform({ name: "f1000research", displayName: "F1000Research", hostName: "f1000research.com", id: "1", editorialEmail: "
[email protected]", infoEmail: "
[email protected]", usePmcStats: true }); $(function(){R.ui.dropdowns('.dropdown-for-authors, .dropdown-for-about, .dropdown-for-myresearch');}); // $(function(){R.ui.dropdowns('.dropdown-for-referees');}); $(document).ready(function () { if ($(".cookie-warning").is(":visible")) { $(".sticky").css("margin-bottom", "35px"); $(".devices").addClass("devices-and-cookie-warning"); } $(".cookie-warning .close-button").click(function (e) { $(".devices").removeClass("devices-and-cookie-warning"); $(".sticky").css("margin-bottom", "0"); }); $("#tweeter-feed .tweet-message").each(function (i, message) { var self = $(message); self.html(linkify(self.html())); }); $(".partner").on("mouseenter mouseleave", function() { $(this).find(".gray-scale, .colour").toggleClass("is-hidden"); }); }); Sign In Remember me Forgotten your password? Sign In Cancel Email or password not correct. Please try again Please wait... $(function(){ // Note: All the setup needs to run against a name attribute and *not* the id due the clonish // nature of facebox... $("a[id=googleSignInButton]").click(function(event){ event.preventDefault(); $("input[id=oAuthSystem]").val("GOOGLE"); $("form[id=oAuthForm]").submit(); }); $("a[id=facebookSignInButton]").click(function(event){ event.preventDefault(); $("input[id=oAuthSystem]").val("FACEBOOK"); $("form[id=oAuthForm]").submit(); }); $("a[id=orcidSignInButton]").click(function(event){ event.preventDefault(); $("input[id=oAuthSystem]").val("ORCID"); $("form[id=oAuthForm]").submit(); }); }); If you've forgotten your password, please enter your email address below and we'll send you instructions on how to reset your password. The email address should be the one you originally registered with F1000. Email address not valid, please try again You registered with F1000 via Google, so we cannot reset your password. To sign in, please click here . If you still need help with your Google account password, please click here . You registered with F1000 via Facebook, so we cannot reset your password. To sign in, please click here . If you still need help with your Facebook account password, please click here . Code not correct, please try again Reset password Cancel Email us for further assistance. Server error, please try again. If your email address is registered with us, we will email you instructions to reset your password. If you think you should have received this email but it has not arrived, please check your spam filters and/or contact for further assistance. Please wait... Register $(document).ready(function () { signIn.createSignInAsRow($("#sign-in-form-gfb-popup")); $(".target-field").each(function () { var uris = $(this).val().split("/"); if (uris.pop() === "login") { $(this).val(uris.toString().replace(",","/")); } }); });
Text is read by the "Ask this paper" AI Q&A widget below.
Extraction quality varies by source — PMC NXML preserves structure
cleanly, OA-HTML may include some navigation residue, and OA-PDF can
have broken hyphenation. The publisher copy
(via DOI)
is the canonical version.