Guest User

Untitled

a guest
Oct 29th, 2026
31
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.32 KB | None | 0 0
  1. // ==UserScript==
  2. // @name EyeWire II MEC - Image Adjustment Controls
  3. // @namespace https://connectome.quest/
  4. // @version 1.3.0
  5. // @description MEC-only draggable contrast, luminosity, hue and sharpness controls
  6. // @match *://eyewire-ii-community-dot-brain-wire-dot-seung-lab.ue.r.appspot.com/*
  7. // @match *://*.brain-wire-test.org/*
  8. // @match *://*.eyewire.org/*
  9. // @match *://*.r.appspot.com/*
  10. // @run-at document-idle
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. if (window.__EW_MEC_IMAGE_CONTROLS__) return;
  19. window.__EW_MEC_IMAGE_CONTROLS__ = true;
  20.  
  21. const STORAGE_KEY = 'ew_mec_image_controls_v1';
  22.  
  23. const defaults = {
  24. contrast: 100,
  25. brightness: 100,
  26. hue: 0,
  27. sharpness: 0,
  28. side: 'left',
  29. x: 20,
  30. y: 100,
  31. collapsed: true
  32. };
  33.  
  34. let saved = {};
  35.  
  36. try {
  37. saved = JSON.parse(
  38. localStorage.getItem(STORAGE_KEY)
  39. ) || {};
  40. } catch (e) {
  41. saved = {};
  42. }
  43.  
  44. const settings = Object.assign({}, defaults, saved);
  45. const originalStyles = new Map();
  46.  
  47. function save() {
  48. try {
  49. localStorage.setItem(
  50. STORAGE_KEY,
  51. JSON.stringify(settings)
  52. );
  53. } catch (e) {
  54. console.warn('[MEC Controls] Save failed:', e);
  55. }
  56. }
  57.  
  58. // --------------------------------------------------
  59. // MEC DATASET DETECTION
  60. // --------------------------------------------------
  61.  
  62. const MEC_IDENTIFIERS = [
  63. 'mec_alignment_2025-09',
  64. 'pni_mec'
  65. ];
  66.  
  67. function isMECDataset() {
  68. let state = window.location.hash || '';
  69.  
  70. try {
  71. state = decodeURIComponent(state);
  72. } catch (e) {
  73. // Use original state.
  74. }
  75.  
  76. state = state.toLowerCase();
  77.  
  78. return MEC_IDENTIFIERS.some(
  79. identifier => state.includes(identifier)
  80. );
  81. }
  82.  
  83. // --------------------------------------------------
  84. // SHARPENING FILTER
  85. // --------------------------------------------------
  86.  
  87. const svgNS = 'http://www.w3.org/2000/svg';
  88.  
  89. const svg = document.createElementNS(svgNS, 'svg');
  90.  
  91. svg.setAttribute('width', '0');
  92. svg.setAttribute('height', '0');
  93.  
  94. svg.style.position = 'absolute';
  95. svg.style.pointerEvents = 'none';
  96.  
  97. const defs = document.createElementNS(svgNS, 'defs');
  98. const filter = document.createElementNS(svgNS, 'filter');
  99.  
  100. filter.id = 'ew-mec-sharpen';
  101.  
  102. filter.setAttribute('x', '0');
  103. filter.setAttribute('y', '0');
  104. filter.setAttribute('width', '100%');
  105. filter.setAttribute('height', '100%');
  106. filter.setAttribute(
  107. 'color-interpolation-filters',
  108. 'sRGB'
  109. );
  110.  
  111. const convolution = document.createElementNS(
  112. svgNS,
  113. 'feConvolveMatrix'
  114. );
  115.  
  116. convolution.setAttribute('order', '3');
  117. convolution.setAttribute('edgeMode', 'duplicate');
  118. convolution.setAttribute('preserveAlpha', 'true');
  119.  
  120. filter.appendChild(convolution);
  121. defs.appendChild(filter);
  122. svg.appendChild(defs);
  123.  
  124. document.documentElement.appendChild(svg);
  125.  
  126. function updateSharpenKernel() {
  127. const amount = settings.sharpness / 25;
  128.  
  129. const kernel = [
  130. 0, -amount, 0,
  131. -amount, 1 + 4 * amount, -amount,
  132. 0, -amount, 0
  133. ];
  134.  
  135. convolution.setAttribute(
  136. 'kernelMatrix',
  137. kernel.join(' ')
  138. );
  139.  
  140. convolution.setAttribute('divisor', '1');
  141. }
  142.  
  143. // --------------------------------------------------
  144. // FIND NEUROGLANCER CANVASES
  145. // --------------------------------------------------
  146.  
  147. function getCandidateCanvases() {
  148. return Array.from(
  149. document.querySelectorAll('canvas')
  150. ).filter(canvas => {
  151.  
  152. if (canvas.closest('#ew-mec-controls')) {
  153. return false;
  154. }
  155.  
  156. const rect = canvas.getBoundingClientRect();
  157. const style = getComputedStyle(canvas);
  158.  
  159. return (
  160. rect.width > 150 &&
  161. rect.height > 150 &&
  162. style.display !== 'none' &&
  163. style.visibility !== 'hidden'
  164. );
  165. });
  166. }
  167.  
  168. function getTargetCanvases() {
  169. const canvases = getCandidateCanvases();
  170.  
  171. if (!canvases.length) return [];
  172.  
  173. const midpoint = window.innerWidth / 2;
  174.  
  175. return canvases.filter(canvas => {
  176. const rect = canvas.getBoundingClientRect();
  177. const centerX = rect.left + rect.width / 2;
  178.  
  179. return settings.side === 'left'
  180. ? centerX < midpoint
  181. : centerX >= midpoint;
  182. });
  183. }
  184.  
  185. // --------------------------------------------------
  186. // APPLY AND RESTORE FILTERS
  187. // --------------------------------------------------
  188.  
  189. function restoreCanvas(canvas) {
  190. const original = originalStyles.get(canvas);
  191.  
  192. if (!original) return;
  193.  
  194. canvas.style.filter = original.filter;
  195. canvas.style.webkitFilter =
  196. original.webkitFilter;
  197.  
  198. originalStyles.delete(canvas);
  199. }
  200.  
  201. function restoreAllCanvases() {
  202. for (const canvas of Array.from(
  203. originalStyles.keys()
  204. )) {
  205. restoreCanvas(canvas);
  206. }
  207. }
  208.  
  209. function applyFilters() {
  210.  
  211. // Never apply adjustments outside MEC.
  212. if (!isMECDataset()) {
  213. restoreAllCanvases();
  214. return;
  215. }
  216.  
  217. updateSharpenKernel();
  218.  
  219. const targets = new Set(
  220. getTargetCanvases()
  221. );
  222.  
  223. for (const canvas of Array.from(
  224. originalStyles.keys()
  225. )) {
  226. if (
  227. !canvas.isConnected ||
  228. !targets.has(canvas)
  229. ) {
  230. restoreCanvas(canvas);
  231. }
  232. }
  233.  
  234. const filterParts = [
  235. `contrast(${settings.contrast}%)`,
  236. `brightness(${settings.brightness}%)`,
  237. `hue-rotate(${settings.hue}deg)`
  238. ];
  239.  
  240. if (settings.sharpness > 0) {
  241. filterParts.push(
  242. 'url(#ew-mec-sharpen)'
  243. );
  244. }
  245.  
  246. const filterString = filterParts.join(' ');
  247.  
  248. for (const canvas of targets) {
  249.  
  250. if (!originalStyles.has(canvas)) {
  251. originalStyles.set(canvas, {
  252. filter: canvas.style.filter,
  253. webkitFilter:
  254. canvas.style.webkitFilter
  255. });
  256. }
  257.  
  258. canvas.style.filter = filterString;
  259. canvas.style.webkitFilter = filterString;
  260. }
  261. }
  262.  
  263. // --------------------------------------------------
  264. // FLOATING USER INTERFACE
  265. // --------------------------------------------------
  266.  
  267. const root = document.createElement('div');
  268. root.id = 'ew-mec-controls';
  269.  
  270. // Hidden until MEC is detected.
  271. root.style.display = 'none';
  272.  
  273. const style = document.createElement('style');
  274.  
  275. style.textContent = `
  276. #ew-mec-controls {
  277. position: fixed;
  278. z-index: 2147483647;
  279. width: 260px;
  280. color: #eef3ff;
  281. font: 13px Arial, sans-serif;
  282. user-select: none;
  283. box-sizing: border-box;
  284. }
  285.  
  286. #ew-mec-controls * {
  287. box-sizing: border-box;
  288. }
  289.  
  290. #ew-mec-controls .ew-header {
  291. display: flex;
  292. align-items: center;
  293. justify-content: space-between;
  294. gap: 8px;
  295. background: #19283d;
  296. border: 1px solid #526d91;
  297. border-radius: 9px;
  298. padding: 9px 11px;
  299. cursor: move;
  300. box-shadow: 0 4px 18px #0008;
  301. touch-action: none;
  302. }
  303.  
  304. #ew-mec-controls .ew-title {
  305. font-weight: bold;
  306. font-size: 13px;
  307. white-space: nowrap;
  308. }
  309.  
  310. #ew-mec-controls button {
  311. color: #eef3ff;
  312. background: #344b68;
  313. border: 1px solid #607b9c;
  314. border-radius: 5px;
  315. padding: 5px 9px;
  316. cursor: pointer;
  317. }
  318.  
  319. #ew-mec-controls button:hover {
  320. background: #49698e;
  321. }
  322.  
  323. #ew-mec-controls .ew-body {
  324. margin-top: 4px;
  325. padding: 13px;
  326. background: rgba(19, 29, 44, 0.97);
  327. border: 1px solid #526d91;
  328. border-radius: 9px;
  329. box-shadow: 0 5px 20px #0009;
  330. }
  331.  
  332. #ew-mec-controls .ew-control {
  333. margin-bottom: 15px;
  334. }
  335.  
  336. #ew-mec-controls .ew-label {
  337. display: flex;
  338. justify-content: space-between;
  339. margin-bottom: 7px;
  340. }
  341.  
  342. #ew-mec-controls .ew-value {
  343. color: #8ecbff;
  344. font-variant-numeric: tabular-nums;
  345. }
  346.  
  347. #ew-mec-controls input[type="range"] {
  348. width: 100%;
  349. margin: 0;
  350. cursor: pointer;
  351. accent-color: #65b7ff;
  352. }
  353.  
  354. #ew-mec-controls select {
  355. width: 100%;
  356. padding: 7px;
  357. color: white;
  358. background: #293c55;
  359. border: 1px solid #526d91;
  360. border-radius: 5px;
  361. }
  362.  
  363. #ew-mec-controls .ew-reset {
  364. width: 100%;
  365. margin-top: 12px;
  366. }
  367.  
  368. #ew-mec-controls .ew-note {
  369. font-size: 10px;
  370. line-height: 1.4;
  371. color: #9daec3;
  372. margin-top: 11px;
  373. }
  374. `;
  375.  
  376. document.head.appendChild(style);
  377.  
  378. root.innerHTML = `
  379. <div class="ew-header">
  380. <span class="ew-title">◐ MEC Image Controls</span>
  381. <button class="ew-toggle"
  382. title="Expand or collapse">
  383. ${settings.collapsed ? '+' : '−'}
  384. </button>
  385. </div>
  386.  
  387. <div class="ew-body"
  388. style="display:${
  389. settings.collapsed ? 'none' : 'block'
  390. }">
  391.  
  392. <div class="ew-sliders"></div>
  393.  
  394. <label class="ew-label"
  395. for="ew-target-pane">
  396. Target pane
  397. </label>
  398.  
  399. <select id="ew-target-pane">
  400. <option value="left">
  401. Left pane (2D)
  402. </option>
  403. <option value="right">
  404. Right pane
  405. </option>
  406. </select>
  407.  
  408. <button class="ew-reset">
  409. Reset adjustments
  410. </button>
  411.  
  412. <div class="ew-note">
  413. MEC dataset only. Visual adjustments
  414. do not change the underlying data
  415. or annotations.
  416. </div>
  417. </div>
  418. `;
  419.  
  420. document.body.appendChild(root);
  421.  
  422. const header = root.querySelector('.ew-header');
  423. const toggle = root.querySelector('.ew-toggle');
  424. const body = root.querySelector('.ew-body');
  425. const sliders = root.querySelector('.ew-sliders');
  426. const targetSelect =
  427. root.querySelector('#ew-target-pane');
  428. const resetButton =
  429. root.querySelector('.ew-reset');
  430.  
  431. // --------------------------------------------------
  432. // SLIDERS
  433. // --------------------------------------------------
  434.  
  435. const controls = [
  436. {
  437. key: 'contrast',
  438. label: 'Contrast',
  439. min: 0,
  440. max: 300,
  441. step: 1,
  442. unit: '%'
  443. },
  444. {
  445. key: 'brightness',
  446. label: 'Luminosity',
  447. min: 0,
  448. max: 300,
  449. step: 1,
  450. unit: '%'
  451. },
  452. {
  453. key: 'hue',
  454. label: 'Hue',
  455. min: -180,
  456. max: 180,
  457. step: 1,
  458. unit: '°'
  459. },
  460. {
  461. key: 'sharpness',
  462. label: 'Sharpness',
  463. min: 0,
  464. max: 100,
  465. step: 1,
  466. unit: '%'
  467. }
  468. ];
  469.  
  470. const sliderElements = {};
  471.  
  472. for (const control of controls) {
  473.  
  474. const wrapper = document.createElement('div');
  475. wrapper.className = 'ew-control';
  476.  
  477. const label = document.createElement('div');
  478. label.className = 'ew-label';
  479.  
  480. const name = document.createElement('span');
  481. name.textContent = control.label;
  482.  
  483. const value = document.createElement('span');
  484. value.className = 'ew-value';
  485.  
  486. const slider = document.createElement('input');
  487. slider.type = 'range';
  488.  
  489. slider.min = control.min;
  490. slider.max = control.max;
  491. slider.step = control.step;
  492.  
  493. label.append(name, value);
  494. wrapper.append(label, slider);
  495. sliders.appendChild(wrapper);
  496.  
  497. sliderElements[control.key] = {
  498. slider,
  499. value,
  500. control
  501. };
  502.  
  503. slider.addEventListener('input', () => {
  504. settings[control.key] =
  505. Number(slider.value);
  506.  
  507. updateSliderDisplays();
  508. applyFilters();
  509. save();
  510. });
  511. }
  512.  
  513. function updateSliderDisplays() {
  514. for (const control of controls) {
  515.  
  516. const entry =
  517. sliderElements[control.key];
  518.  
  519. entry.slider.value =
  520. settings[control.key];
  521.  
  522. entry.value.textContent =
  523. settings[control.key] +
  524. control.unit;
  525. }
  526. }
  527.  
  528. targetSelect.value = settings.side;
  529.  
  530. targetSelect.addEventListener('change', () => {
  531. settings.side = targetSelect.value;
  532.  
  533. applyFilters();
  534. save();
  535. });
  536.  
  537. resetButton.addEventListener('click', () => {
  538. settings.contrast = 100;
  539. settings.brightness = 100;
  540. settings.hue = 0;
  541. settings.sharpness = 0;
  542.  
  543. updateSliderDisplays();
  544. applyFilters();
  545. save();
  546. });
  547.  
  548. toggle.addEventListener('click', event => {
  549. event.stopPropagation();
  550.  
  551. settings.collapsed = !settings.collapsed;
  552.  
  553. body.style.display =
  554. settings.collapsed ? 'none' : 'block';
  555.  
  556. toggle.textContent =
  557. settings.collapsed ? '+' : '−';
  558.  
  559. save();
  560. });
  561.  
  562. updateSliderDisplays();
  563.  
  564. // --------------------------------------------------
  565. // DRAGGABLE PANEL
  566. // --------------------------------------------------
  567.  
  568. function clampPosition() {
  569.  
  570. const maxX = Math.max(
  571. 0,
  572. window.innerWidth - root.offsetWidth
  573. );
  574.  
  575. const maxY = Math.max(
  576. 0,
  577. window.innerHeight - header.offsetHeight
  578. );
  579.  
  580. settings.x = Math.max(
  581. 0,
  582. Math.min(settings.x, maxX)
  583. );
  584.  
  585. settings.y = Math.max(
  586. 0,
  587. Math.min(settings.y, maxY)
  588. );
  589.  
  590. root.style.left = settings.x + 'px';
  591. root.style.top = settings.y + 'px';
  592. }
  593.  
  594. // Set coordinates even when the panel is hidden.
  595. root.style.left = settings.x + 'px';
  596. root.style.top = settings.y + 'px';
  597.  
  598. let dragging = false;
  599. let dragOffsetX = 0;
  600. let dragOffsetY = 0;
  601.  
  602. header.addEventListener('pointerdown', event => {
  603.  
  604. if (event.target.closest('button')) {
  605. return;
  606. }
  607.  
  608. dragging = true;
  609.  
  610. const rect = root.getBoundingClientRect();
  611.  
  612. dragOffsetX = event.clientX - rect.left;
  613. dragOffsetY = event.clientY - rect.top;
  614.  
  615. header.setPointerCapture(event.pointerId);
  616. event.preventDefault();
  617. });
  618.  
  619. header.addEventListener('pointermove', event => {
  620.  
  621. if (!dragging) return;
  622.  
  623. settings.x = event.clientX - dragOffsetX;
  624. settings.y = event.clientY - dragOffsetY;
  625.  
  626. clampPosition();
  627. });
  628.  
  629. function stopDragging() {
  630.  
  631. if (!dragging) return;
  632.  
  633. dragging = false;
  634. save();
  635. }
  636.  
  637. header.addEventListener('pointerup', stopDragging);
  638. header.addEventListener('pointercancel', stopDragging);
  639.  
  640. window.addEventListener('resize', () => {
  641. if (isMECDataset()) {
  642. clampPosition();
  643. }
  644.  
  645. applyFilters();
  646. });
  647.  
  648. // --------------------------------------------------
  649. // AUTOMATIC DATASET SWITCHING
  650. // --------------------------------------------------
  651.  
  652. let lastDatasetState = null;
  653.  
  654. function updateDatasetVisibility() {
  655.  
  656. const isMEC = isMECDataset();
  657.  
  658. if (isMEC !== lastDatasetState) {
  659.  
  660. lastDatasetState = isMEC;
  661.  
  662. if (isMEC) {
  663.  
  664. root.style.display = 'block';
  665. clampPosition();
  666. applyFilters();
  667.  
  668. console.log(
  669. '[MEC Controls] MEC detected. Controls enabled.'
  670. );
  671.  
  672. } else {
  673.  
  674. root.style.display = 'none';
  675. restoreAllCanvases();
  676.  
  677. console.log(
  678. '[MEC Controls] Non-MEC dataset. Controls disabled.'
  679. );
  680. }
  681. }
  682. }
  683.  
  684. window.addEventListener(
  685. 'hashchange',
  686. updateDatasetVisibility
  687. );
  688.  
  689. // Detect dataset changes made without reloading.
  690. setInterval(updateDatasetVisibility, 1500);
  691.  
  692. // Keep filters applied to newly created canvases,
  693. // but only while MEC is active.
  694. let scheduled = false;
  695.  
  696. function scheduleApply() {
  697.  
  698. if (scheduled || !isMECDataset()) {
  699. return;
  700. }
  701.  
  702. scheduled = true;
  703.  
  704. requestAnimationFrame(() => {
  705. scheduled = false;
  706.  
  707. if (isMECDataset()) {
  708. applyFilters();
  709. }
  710. });
  711. }
  712.  
  713. const observer = new MutationObserver(
  714. scheduleApply
  715. );
  716.  
  717. observer.observe(document.body, {
  718. childList: true,
  719. subtree: true
  720. });
  721.  
  722. setInterval(() => {
  723. if (isMECDataset()) {
  724. applyFilters();
  725. }
  726. }, 2000);
  727.  
  728. // Initial detection.
  729. updateDatasetVisibility();
  730.  
  731. console.log(
  732. '[EyeWire MEC Controls] v1.3.0 initialized.'
  733. );
  734.  
  735. })();
Advertisement
Comments
  • User was banned
Add Comment
Please, Sign In to add comment