UNPKG

@sfgrp/sled

Version:

UI for image segmentation of slide collections

574 lines (573 loc) 16 kB
import { computed as z, openBlock as L, createElementBlock as S, normalizeStyle as W, ref as k, onMounted as Z, onBeforeUnmount as _, createElementVNode as D, Fragment as q, renderList as I, createBlock as E, createVNode as G, createCommentVNode as T, withDirectives as X, toDisplayString as ue, vModelSelect as se, vModelCheckbox as oe, mergeModels as K, useModel as P, watch as H } from "vue"; const re = ["x1", "y1", "x2", "y2"], J = { __name: "SvgLine", props: { x1: { type: [Number, String], required: !0 }, x2: { type: [Number, String], required: !0 }, y1: { type: [Number, String], required: !0 }, y2: { type: [Number, String], required: !0 }, scale: { type: [Number, String], required: !0 }, lineThickness: { type: [Number, String] } }, setup(e) { const M = e, u = z(() => M.y1 === M.y2), o = z(() => ({ stroke: "rgb(255,0,0)", strokeWidth: M.lineThickness, strokeLinecap: "round", cursor: u.value ? "ns-resize" : "ew-resize" })); return (a, i) => (L(), S("line", { x1: e.x1 / e.scale, y1: e.y1 / e.scale, x2: e.x2 / e.scale, y2: e.y2 / e.scale, style: W(o.value) }, null, 12, re)); } }, ce = ["cx", "cy"], Q = { __name: "SvgCircle", props: { hLines: { type: Array, required: !0 }, vLines: { type: Array, required: !0 }, scale: { type: Number, default: 1 }, ix: { type: Number, default: 0 }, iy: { type: Number, default: 0 }, strokeColor: { type: String, default: "black" } }, emits: ["dragging"], setup(e, { emit: M }) { const u = e, o = M; function a() { o("dragging", [u.ix, u.iy]); } const i = z( () => u.ix < 0 ? (0.7 * u.vLines[0] + 0.3 * u.vLines[u.vLines.length - 1]) / u.scale : u.vLines[u.ix] / u.scale ), v = z( () => u.iy < 0 ? (0.7 * u.hLines[0] + 0.3 * u.hLines[u.hLines.length - 1]) / u.scale : u.hLines[u.iy] / u.scale ), s = z(() => ({ stroke: u.strokeColor, strokeWidth: 2, strokeOpacity: 0.7, fillOpacity: 0, zIndex: 3 })); return (x, d) => (L(), S("circle", { cx: i.value, cy: v.value, r: 10, style: W(s.value), onMousedown: a }, null, 44, ce)); } }, ve = ["width", "height"], de = ["width", "height", "xlink:href"], ge = { __name: "SvgComponent", props: { imageData: { type: String, required: !0 }, imageWidth: { type: Number, required: !0 }, imageHeight: { type: Number, required: !0 }, scale: { type: Number, default: 1 }, hLines: { type: Array, required: !0 }, vLines: { type: Array, required: !0 }, lineThickness: { type: Number } }, emits: [ "circleUL", "circleLR", "dragUL", "dragLR", "dragVline", "dragHline" ], setup(e, { emit: M }) { const u = e, o = M; k([0, 0, -1]), k([0, 0, -1]); const a = k(!1), i = k(), v = k([]), s = k(), x = k(); function d() { a.value = !1, s.value = void 0; } function g(b = 0) { return Math.random().toString(16).substr(2, 8) + b; } function h({ clientX: b, clientY: r }) { var m; if (a.value) { const c = (m = x.value) == null ? void 0 : m.getBoundingClientRect(); v.value; const w = b - c.left - u.vLines[v.value[0]] / u.scale, V = r - c.top - u.hLines[v.value[1]] / u.scale; s.value ? o(s.value, [w, V]) : v.value[0] >= 0 ? o("dragVline", [w, V, v.value[0]]) : o("dragHline", [w, V, v.value[1]]); } } return Z(() => { window.addEventListener("mouseup", d), window.addEventListener("mousemove", h); }), _(() => { window.removeEventListener("mouseup", d), window.removeEventListener("mousemove", h); }), (b, r) => (L(), S("svg", { ref_key: "rootRef", ref: x, width: e.imageWidth / e.scale, height: e.imageHeight / e.scale, style: { zIndex: 2, position: "absolute" } }, [ D("image", { xmlns: "http://www.w3.org/2000/svg", "xmlns:xlink": "http://www.w3.org/1999/xlink", width: e.imageWidth / e.scale, height: e.imageHeight / e.scale, "xlink:href": e.imageData }, null, 8, de), e.hLines.length > 1 && e.vLines.length > 1 ? (L(), S(q, { key: 0 }, [ (L(!0), S(q, null, I(e.hLines, (m, c) => (L(), E(J, { key: g(c), x1: e.vLines[0], y1: e.hLines[c], x2: e.vLines[e.vLines.length - 1], y2: e.hLines[c], scale: e.scale, "line-thickness": e.lineThickness, onMousedown: () => { a.value = !0, v.value = [-1, c]; } }, null, 8, ["x1", "y1", "x2", "y2", "scale", "line-thickness", "onMousedown"]))), 128)), (L(!0), S(q, null, I(e.vLines, (m, c) => (L(), E(J, { key: g(c), x1: e.vLines[c], y1: e.hLines[0], x2: e.vLines[c], y2: e.hLines[e.hLines.length - 1], scale: e.scale, "line-thickness": e.lineThickness, index: c, dragging: a.value, onMousedown: () => { a.value = !0, v.value = [c, -1]; }, onMousemove: r[0] || (r[0] = (w) => i.value = w) }, null, 8, ["x1", "y1", "x2", "y2", "scale", "line-thickness", "index", "dragging", "onMousedown"]))), 128)), G(Q, { ix: 0, iy: 0, "h-lines": e.hLines, "v-lines": e.vLines, scale: e.scale, "stroke-color": "red", style: { cursor: "move" }, onDragging: r[1] || (r[1] = (m) => { a.value = !0, s.value = "dragUL", v.value = m; }) }, null, 8, ["h-lines", "v-lines", "scale"]), G(Q, { ix: e.vLines.length - 1, iy: e.hLines.length - 1, "h-lines": e.hLines, "v-lines": e.vLines, scale: e.scale, "stroke-color": "red", style: { cursor: "nwse-resize" }, onDragging: r[2] || (r[2] = (m) => { a.value = !0, s.value = "dragLR", v.value = m; }) }, null, 8, ["ix", "iy", "h-lines", "v-lines", "scale"]) ], 64)) : T("", !0) ], 8, ve)); } }, he = ["innerHTML"], me = ["disabled"], ye = /* @__PURE__ */ D("option", { value: "none" }, "None", -1), fe = ["value"], Le = ["disabled"], R = 5, xe = { __name: "Cell", props: { modelValue: { type: Object, required: !0 }, metadata: { type: Object, default: void 0 }, scale: { type: Number, default: 1 }, locked: { type: Boolean, default: !1 } }, emits: ["update:modelValue", "onChange"], setup(e, { emit: M }) { const u = { position: "absolute", right: "10px", bottom: "10px" }, o = { position: "absolute", top: "4px", backgroundColor: "#FFF", borderRadius: "3px", padding: "4px", opacity: 0.9 }, a = { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)" }, i = e, v = M, s = z({ get() { return i.modelValue; }, set(g) { v("update:modelValue", g); } }), x = z({ get() { return !s.value.metadata; }, set(g) { s.value.metadata = g ? null : s.value.metadata || "none"; } }), d = z(() => { const { upperCorner: g, lowerCorner: h } = s.value; return { position: "absolute", top: `${g.y / i.scale + R}px`, left: `${g.x / i.scale + R}px`, width: `${(h.x - g.x) / i.scale - R * 2}px`, height: `${(h.y - g.y) / i.scale - R * 2}px`, zIndex: 2 }; }); return (g, h) => { var b; return L(), S("div", { style: W(d.value) }, [ (b = s.value) != null && b.textfield ? (L(), S("span", { key: 0, class: "cell-textfield", style: o, innerHTML: s.value.textfield }, null, 8, he)) : T("", !0), x.value ? T("", !0) : X((L(), S("select", { key: 1, class: "cell-select", "onUpdate:modelValue": h[0] || (h[0] = (r) => s.value.metadata = r), disabled: e.locked, style: a, onChange: h[1] || (h[1] = (r) => v("onChange", s.value)) }, [ ye, (L(!0), S(q, null, I(e.metadata, (r, m) => (L(), S("option", { key: m, value: m }, ue(r), 9, fe))), 128)) ], 40, me)), [ [se, s.value.metadata] ]), X(D("input", { type: "checkbox", class: "cell-checkbox", disabled: e.locked, style: u, "onUpdate:modelValue": h[2] || (h[2] = (r) => x.value = r) }, null, 8, Le), [ [oe, x.value] ]) ], 4); }; } }, be = /* @__PURE__ */ Object.assign({ name: "Sled" }, { __name: "Sled", props: /* @__PURE__ */ K({ metadataAssignment: { type: Object, default: () => ({}) }, imageWidth: { type: Number, required: !0 }, imageHeight: { type: Number, required: !0 }, fileImage: { type: String }, lineWeight: { type: [Number, String], default: 4 }, autosize: { type: Boolean, default: !0 }, locked: { type: Boolean, default: !1 } }, { verticalLines: { type: Array, required: !0 }, verticalLinesModifiers: {}, horizontalLines: { type: Array, required: !0 }, horizontalLinesModifiers: {} }), emits: /* @__PURE__ */ K(["onComputeCells", "resize"], ["update:verticalLines", "update:horizontalLines"]), setup(e, { expose: M, emit: u }) { const o = e, a = P(e, "verticalLines"), i = P(e, "horizontalLines"), v = u, s = k(0), x = k(0), d = k([]), g = k(0), h = k(0), b = k(void 0), r = k(1), m = k(null), c = z(() => i.value.length - 1), w = z(() => a.value.length - 1); H( [i, a], () => { const l = F(i.value), n = F(a.value); l.isSorted || (i.value = l.arr), n.isSorted || (a.value = n.arr), te(); }, { deep: !0, immediate: !0 } ), H( () => o.imageHeight, (l) => { x.value = l, B(); }, { immediate: !0 } ), H( () => o.imageWidth, (l) => { s.value = l, B(); }, { immediate: !0 } ), H( () => o.fileImage, (l) => { g.value = s.value, h.value = x.value, B(); } ), H( () => o.autosize, (l) => { l ? (b.value = new ResizeObserver(Y), b.value.observe(m.value)) : b.value.disconnect(), r.value = j(); } ), Z(() => { o.autosize && (b.value = new ResizeObserver(Y), b.value.observe(m.value)); }), _(() => { b.value.disconnect(); }); function V(l, n) { d.value[l] = n, v("onComputeCells", d.value); } function p(l, n) { const [t] = l; return t + n < 0; } function U(l) { if (!p(a.value, l)) for (let n = 0; n < a.value.length; n++) $(n, l); } function A(l) { if (!p(i.value, l)) for (let n = 0; n < i.value.length; n++) O(n, l); } function $(l, n) { const t = Math.round(a.value[l] + n); t < 0 || t > o.imageWidth || (a.value[l] = t); } function O(l, n) { const t = Math.round(i.value[l] + n); t < 0 || t > o.imageHeight || (i.value[l] = t); } function F(l) { const n = l.toSorted((t, y) => t - y); return { isSorted: n.every((t, y) => l[y] === t), arr: n }; } function B() { if (g.value > 1) { const l = s.value / g.value, n = a.value.length; let t = 0; for (t = 0; t < n; t++) a.value[t] = Math.round(a.value[t] * l); } if (h.value > 1) { const l = x.value / h.value, n = i.value.length; let t = 0; for (t = 0; t < n; t++) i.value[t] = Math.round(i.value[t] * l); } g.value = s.value, h.value = x.value; } function ee() { if (i.value.length > 1 && a.value.length > 1) { d.value = []; const l = (i.value[c.value] - i.value[0]) / c.value, n = (a.value[w.value] - a.value[0]) / w.value; for (let t = 0; t < c.value; t++) i.value[t] = Math.round(i.value[0] + t * l); for (let t = 0; t < w.value; t++) a.value[t] = Math.round(a.value[0] + t * n); } } function te() { if (i.value.length > 0 && a.value.length > 0) { let l, n, t = -1; for (let y = 0; y < c.value; y++) for (let C = 0; C < w.value; C++) { t = w.value * y + C, l = { x: a.value[C], y: i.value[y] }, n = { x: a.value[C + 1], y: i.value[y + 1] }; const { metadata: f = null, textfield: N } = d.value[t] || {}; d.value[t] = { index: t, upperCorner: l, lowerCorner: n, row: y, column: C, metadata: f, textfield: N }; } d.value = t === -1 ? [] : d.value.slice(0, t + 1), v("onComputeCells", d.value); } } function le(l) { const [n, t] = l, y = a.value.at(-1), C = i.value.at(-1); if (!(y + n > o.imageWidth || C + t > o.imageHeight)) { for (let f = 1; f < a.value.length; f++) { const N = Math.round(a.value[f] + f * n / w.value); N > 0 && N <= o.imageWidth && (a.value[f] = Math.round(a.value[f] + f * n / w.value)); } for (let f = 1; f < i.value.length; f++) { const N = Math.round(i.value[f] + f * t / c.value); N > 0 && N <= o.imageHeight && (i.value[f] = Math.round(i.value[f] + f * t / c.value)); } } } function ne(l) { const [n, t] = l; U(n), A(t); } function ae(l) { const n = l[1], t = l[2]; O(t, n); } function ie(l) { const n = l[0], t = l[2]; $(t, n); } function j() { if (o.autosize) { const l = m.value.getBoundingClientRect(), n = l.height < x.value ? x.value / l.height : 1, t = l.width < s.value ? s.value / l.width : 1; return n > t ? n : t; } else return 1; } function Y(l, n) { const t = m.value.getBoundingClientRect(); r.value = j(), v("resize", { ...t, scale: r.value }); } return M({ moveX: U, moveY: A, moveH: O, moveV: $, equalizeLines: ee }), (l, n) => (L(), S("div", { ref_key: "rootRef", ref: m, style: W({ display: "block", position: "relative", height: `${x.value}px` }) }, [ e.fileImage ? (L(), E(ge, { key: 0, "image-width": s.value, "image-height": x.value, "image-data": e.fileImage, "h-lines": i.value, "v-lines": a.value, scale: r.value, "line-thickness": e.lineWeight, onDragUL: ne, onDragLR: le, onDragHline: ae, onDragVline: ie }, null, 8, ["image-width", "image-height", "image-data", "h-lines", "v-lines", "scale", "line-thickness"])) : T("", !0), (L(!0), S(q, null, I(d.value, (t, y) => (L(), E(xe, { key: y, locked: e.locked, metadata: e.metadataAssignment, scale: r.value, modelValue: d.value[y], "onUpdate:modelValue": (C) => d.value[y] = C, onOnChange: (C) => V(y, C) }, null, 8, ["locked", "metadata", "scale", "modelValue", "onUpdate:modelValue", "onOnChange"]))), 128)) ], 4)); } }); export { be as default };