diff --git a/cf.js b/cf.js
index 8589b95ebe757cba5c535c4d753921444f4d6065..860be38f127fb8c735a25eda3d86e88e461faf84 100644
--- a/cf.js
+++ b/cf.js
@@ -7,14 +7,20 @@ const bodyparser = require('body-parser')
 // our fs tools,
 const fs = require('fs')
 const filesys = require('./filesys.js')
+// and we occasionally spawn local pipes (workers)
+const {
+  Worker, workerData
+} = require('worker_threads')
 // will use these to figure where tf we are
 const os = require('os')
 let ifaces = os.networkInterfaces()
 
 // serve everything: https://expressjs.com/en/resources/middleware/serve-static.html
 app.use(express.static(__dirname))
+// accept post bodies as json,
 app.use(bodyparser.json())
 app.use(bodyparser.urlencoded({extended: true}))
+
 // if these don't exist, they get 'nexted' to any other 'middleware' we write
 app.get('/fileList', (req, res) => {
   try{
@@ -42,6 +48,7 @@ app.get('/fileList', (req, res) => {
     res.send('server-side error retrieving list')
   }
 })
+
 // we also handle file-saving this way,
 app.post('/save/contexts/:context/:file', (req, res) => {
   // this is probably fine for now, but I kind of want a websocket to do this kind of stuff ?
@@ -68,11 +75,35 @@ app.post('/save/systems/:file', (req, res) => {
   })
 })
 
+// we also want to institute some pipes: this is a holdover for a better system
+// more akin to nautilus, where server-side graphs are manipulated
+// for now, we just want to dive down to a usb port, probably, so this shallow link is OK
+app.get('/pipeHookup/:file', (req, res) => {
+  // we can assume that the file is a reciprocal pipe-type in our local pipes/file.js location
+  // we'll open that can as a spawn, can assume it's hosting a websocket (it will tell us the port?)
+  // and we can send that information back up stream,
+  console.log('/pipes', req.params.file)
+  const piper = new Worker(`${__dirname}/pipes/${req.params.file}`)
+  piper.on('message', (msg) => {
+    console.log('worker msg', msg)
+  })
+  piper.on('error', (err) => {
+    console.log('worker err', err)
+  })
+  piper.on('exit', (code) => {
+    console.log('exit code', code)
+  })
+  res.send({address: 'localip', port: '1024'})
+  // then this (or similar) should be all we really need to add here, and we can do local-dev of the pipes in hunks/pipes/pipename.js and pipes/pipename.js
+  // so, do we spawn, or do we use workers ?
+  // awh yis it's workers, messages easy, errors also OK to catch... nextup: draw the sys, what wraps what doesn't? mostly: want to be able to refresh / reload / restart remotely
+})
+
+// finally, we tell the thing to listen here:
 let port = 8080
-// and listen,
 app.listen(port)
 
-// want to announce our existence, this just logs our IPs to the console:
+// this just logs the processes IP's to the termina
 Object.keys(ifaces).forEach(function(ifname) {
   var alias = 0;
 
@@ -93,20 +124,3 @@ Object.keys(ifaces).forEach(function(ifname) {
     ++alias;
   });
 });
-
-
-/*
-let begin = () => {
-  // setup to dish files,
-  ex.get('/', (req, res) => {
-    console.log('req /')
-    res.sendFile(__dirname + '/index.html')
-  })
-
-  http.listen(8080, () => {
-    console.log("is listening on 8080")
-  })
-}
-
-begin()
-*/
diff --git a/gogetter.js b/gogetter.js
index 767eb1cb682cb1544b240f58727ee1bad5847898..2be6b76bb9fc3d69d8a0537659333b221e823500 100644
--- a/gogetter.js
+++ b/gogetter.js
@@ -8,7 +8,7 @@ function GoGetter() {
   this.recursivePathSearch = (root, debug) => {
     return new Promise((resolve, reject) => {
       jQuery.get(`/fileList?path=${root}`, (resp) => {
-        console.log('resp at jq', resp)
+        //console.log('resp at jq', resp)
         resolve(resp)
       })
     })
diff --git a/hunks/hunks.js b/hunks/hunks.js
index 3ad3bfaf4b5ccb7f0886807bac767281c3af02ce..eae5b6f3a3c8a8731f3954768f40c77e69c69799 100644
--- a/hunks/hunks.js
+++ b/hunks/hunks.js
@@ -325,6 +325,10 @@ function deepCopy(obj) {
   // turns out parse/stringify is the fastest,
   //console.log('to dc', obj)
   //console.log(JSON.stringify(obj))
+  if(obj instanceof ImageData) {
+    let newData = new ImageData(obj.data, obj.width, obj.height)
+    return newData
+  }
   return JSON.parse(JSON.stringify(obj))
 }
 
diff --git a/hunks/image/readpng_leo.js b/hunks/image/readpng_leo.js
new file mode 100644
index 0000000000000000000000000000000000000000..2426ce644dad34f3fcf591cb48bac7bb538e620b
--- /dev/null
+++ b/hunks/image/readpng_leo.js
@@ -0,0 +1,107 @@
+/*
+
+hunk template
+
+*/
+
+// these are ES6 modules
+import {
+  Hunkify,
+  Input,
+  Output,
+  State
+} from '../hunks.js'
+
+import {
+  html,
+  svg,
+  render
+} from 'https://unpkg.com/lit-html?module';
+
+export default function UploadPNG() {
+  // this fn attaches handles to our function-object,
+  Hunkify(this)
+
+  const imageOut = new Output('RGBA', 'Image', this)
+  this.outputs.push(imageOut)
+
+  this.init = () => {
+    this.dom = $('<div>').get(0)
+  }
+
+  //import
+  const png_read_handler = (e) => {
+    const reader = new FileReader();
+
+    const files = e.target.files;
+    const file = files[0];
+
+    const img = new Image();
+    img.file = file;
+
+    const loader = (aImg) => (e) => {
+      aImg.src = e.target.result;
+
+      aImg.onload = () => {
+        const canvas = document.getElementById("wackyAndTotallyUniqueID2");
+        const ctx = canvas.getContext("2d");
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+        ctx.drawImage(aImg, 0, 0);
+
+        // string other functions here
+        const data = getImageDataRGBA();
+        // console.log("data", data);
+
+        imageOut.put(data)
+        // console.log(data);
+        // console.log("copied", JSON.stringify(data));
+      }
+
+    };
+    reader.onload = loader(img);
+    reader.readAsDataURL(file);
+  }
+
+  // get image data: RGBA
+  const getImageDataRGBA = () => { //should add dpi term here
+    const canvas = document.getElementById("wackyAndTotallyUniqueID2");
+    const ctx = canvas.getContext("2d");
+    const imageRGBA = ctx.getImageData(0, 0, canvas.width, canvas.height);
+    return imageRGBA;
+  }
+
+  this.onload = () => {
+    const target = $('<div>').get(0);
+    $(this.dom).append(target);
+
+    const view = html `
+      <style>
+        #wackyAndTotallyUniqueID2 {
+          border: 1px solid black;
+          margin: 5px;
+        }
+
+        #wackyAndTotallyUniqueID {
+          position: absolute;
+          left: 10px;
+          top: 350px;
+        }
+      </style>
+      <div>Upload!</div>
+      <canvas
+        id="wackyAndTotallyUniqueID2"
+        width=300
+        height=300>
+      </canvas>
+      <input
+        id="wackyAndTotallyUniqueID"
+        type="file"
+        accept="image/png"
+        @input=${(e) => png_read_handler(e)}></input>
+    `
+    render(view, target);
+  }
+
+
+  this.loop = () => {}
+}
diff --git a/hunks/image/thresholdrgba_leo.js b/hunks/image/thresholdrgba_leo.js
new file mode 100644
index 0000000000000000000000000000000000000000..fcd4d6f3805778f6a3a6c6e9c99a14b1e119d5f5
--- /dev/null
+++ b/hunks/image/thresholdrgba_leo.js
@@ -0,0 +1,109 @@
+/*
+
+hunk template
+
+*/
+
+// these are ES6 modules
+import {
+  Hunkify,
+  Input,
+  Output,
+  State
+} from '../hunks.js'
+
+import {
+  html,
+  svg,
+  render
+} from 'https://unpkg.com/lit-html?module';
+
+
+export default function Threshold() {
+  // this fn attaches handles to our function-object,
+  Hunkify(this)
+
+  // inputs
+  let imageIn = new Input('RGBA', 'Image', this);
+  this.inputs.push(imageIn);
+
+  // states
+  let threshold = new State('number', 'Threshold', .5);
+  this.states.push(threshold);
+
+  // outputs
+  let imageOut = new Output('RGBA', 'Image', this);
+  this.outputs.push(imageOut);
+
+  // Helper Functions
+  const thresholdRGBA = (imageRGBA, threshold) => {
+    console.log(imageRGBA)
+    const w = imageRGBA.width;
+    const h = imageRGBA.height;
+    const buf = imageRGBA.data;
+    const t = threshold;
+
+    let r, g, b, a, i;
+    for (var row = 0; row < h; ++row) {
+      for (var col = 0; col < w; ++col) {
+        r = buf[(h - 1 - row) * w * 4 + col * 4 + 0];
+        g = buf[(h - 1 - row) * w * 4 + col * 4 + 1];
+        b = buf[(h - 1 - row) * w * 4 + col * 4 + 2];
+        a = buf[(h - 1 - row) * w * 4 + col * 4 + 3];
+        i = (r + g + b) / (3 * 255);
+
+        let val;
+        if (a === 0) {
+          val = 255;
+        } else if (i > t) {
+          val = 255;
+        } else {
+          val = 0;
+        }
+
+        buf[(h - 1 - row) * w * 4 + col * 4 + 0] = val;
+        buf[(h - 1 - row) * w * 4 + col * 4 + 1] = val;
+        buf[(h - 1 - row) * w * 4 + col * 4 + 2] = val;
+        buf[(h - 1 - row) * w * 4 + col * 4 + 3] = 255;
+      }
+    }
+
+    const imgdata = new ImageData(buf, w, h)
+
+    return imgdata;
+  }
+
+  // view
+  // as is tradition
+  this.dom = {}
+  this.init = () => {
+    this.dom = $('<div>').get(0)
+  }
+
+  this.onload = () => {
+    const target = $('<div>').get(0);
+    $(this.dom).append(target);
+
+    const view = html `
+    <button
+      @mousedown=${() => {
+        // console.log("imageIn", imageIn.get());
+        // console.log("copied", deepCopy(imageIn));
+        const newOut = thresholdRGBA(imageIn.get(), threshold.value)
+        imageOut.put(newOut);
+      }}>
+      Calculate
+    </button>
+    `
+
+    render(view, target);
+  }
+
+  //loop
+  this.loop = () => {
+    if(imageIn.io() && !imageOut.io()){
+      const thresholded = thresholdRGBA(imageIn.get(), threshold.value)
+      imageOut.put(thresholded)
+    }
+  }
+}
diff --git a/hunks/pipes/pipetemplate.js b/hunks/pipes/pipetemplate.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a7c5f3740c72ff4535a60de8d3772ae9c416149
--- /dev/null
+++ b/hunks/pipes/pipetemplate.js
@@ -0,0 +1,34 @@
+/*
+
+pipes are websocket-having devices that commune with our server,
+this is a scratch / example of one such object
+
+*/
+
+import {
+  Hunkify,
+  Input,
+  Output,
+  State
+} from '../hunks.js'
+
+export default function Pipe() {
+  Hunkify(this)
+  let debug = true
+
+  let statusMessage = new State('string', 'status', 'closed')
+  let retryButton = new State('boolean', 'retry', false)
+  this.states.push(statusMessage, retryButton)
+
+  // coming merge of init and onload, however:
+  this.init = () => {
+    // hijack ajax to ask for a websocket
+    jQuery.get('pipeHookup/pipetemplate.js', (data) => {
+      console.log('pipe jquery data', data)
+    })
+  }
+
+  this.loop = () => {
+    // ws status, ws messages ...
+  }
+}
diff --git a/hunks/pipes/vfpt.js b/hunks/pipes/vfpt.js
new file mode 100644
index 0000000000000000000000000000000000000000..5627185e97dd63dbe09604aaf9e8d56d1211a033
--- /dev/null
+++ b/hunks/pipes/vfpt.js
@@ -0,0 +1,164 @@
+/*
+
+very fast ~~picket ship~~ pipe transport
+
+*/
+
+import {
+  Hunkify,
+  Input,
+  Output,
+  State
+} from '../hunks.js'
+
+function VFPT() {
+  Hunkify(this)
+
+  let debug = false
+
+  let dtin = new Input('byteArray', 'data', this)
+  this.inputs.push(dtin)
+
+  let dtout = new Output('byteArray', 'data', this)
+  this.outputs.push(dtout)
+
+  // TODO is tackling state sets / updates / onupdate fn's
+  // this is hunk -> manager commune ...
+  let statusMessage = new State('string', 'status', 'closed')
+  let retryCountHandle = new State('number', 'retrycount', 3)
+  let resetRetryHandle = new State('boolean', 'retryreset', false)
+  let addressState = new State('string', 'address', '127.0.0.1')
+  let portState = new State('number', 'port', 2042)
+  this.states.push(statusMessage, retryCountHandle, resetRetryHandle, addressState, portState)
+
+  // this ws is a client,
+  let ws = {}
+  let url = 'ws://127.0.0.1:2020'
+  this.outbuffer = new Array()
+
+  this.init = () => {
+    setTimeout(startWs, 500)
+  }
+
+  resetRetryHandle.change = (value) => {
+    retryCountHandle.set(3)
+    startWs()
+    // to actually change the value, we would do:
+    // resetRetryHandle.set(value)
+  }
+
+  let startWs = () => {
+    // manager calls this once
+    // it is loaded and state is updated (from program)
+    url = 'ws://' + addressState.value + ':' + portState.value
+    this.log(`attempt start ws at ${url}`)
+    ws = new WebSocket(url)
+    ws.binaryType = "arraybuffer"
+    ws.onopen = (evt) => {
+      this.log('ws opened')
+      statusMessage.set('open')
+    }
+    ws.onerror = (evt) => {
+      this.log('ws error, will reset to check')
+      console.log('ws error:', evt)
+      if(debug) console.log(evt)
+      statusMessage.set('error')
+      setCheck(500)
+    }
+    ws.onclose = (evt) => {
+      this.log('ws close')
+      setCheck(500)
+    }
+    ws.onmessage = (message) => {
+      // this should be a buffer
+      if(debug) console.log('WS receives', message.data)
+      // tricks?
+      // ok, message.data is a blob, we know it's str8 up bytes, want that
+      // as an array
+      let msgAsArray = new Uint8Array(message.data)
+      // it's messy, yep!
+      let msgAsStdArray = Array.from(msgAsArray)
+      if(debug) console.log('WS receive, as an array:', msgAsArray);
+      if (dtout.ie && this.outbuffer.length === 0) {
+        dtout.put(msgAsStdArray)
+      } else {
+        this.outbuffer.push(msgAsStdArray)
+      }
+    }
+    statusMessage.set('ws initialized...')
+  }
+
+  let checking = false
+
+  let setCheck = (ms) => {
+    if (checking) {
+      // noop
+    } else {
+      setTimeout(checkWsStatus, ms)
+      checking = true
+    }
+  }
+
+  let checkWsStatus = () => {
+    let retrycount = retryCountHandle.value - 1
+    if (retrycount < 1) {
+      // give up
+      statusMessage.set('not connected')
+      retryCountHandle.set(0)
+      checking = false
+    } else {
+      retryCountHandle.set(retrycount)
+      checking = false
+      this.log('CHECKING STATUS')
+      switch (ws.readyState) {
+        case WebSocket.CONNECTING:
+          this.log('ws is in process of connecting...')
+          break
+        case WebSocket.OPEN:
+          this.log('is open')
+          break
+        case WebSocket.CLOSING:
+          this.log('is closing')
+          break
+        case WebSocket.CLOSED:
+          this.log('is closed, retrying ...')
+          startWs()
+          break
+        default:
+          throw new Error('nonsensical result at ws readystate check for ws')
+          break
+      }
+    }
+  }
+
+  // override default change f'n
+  retryCountHandle.change = (value) => {
+    this.log('retrycount reset')
+    retryCountHandle.set(value)
+    setCheck(10)
+  }
+
+  this.loop = () => {
+    // something like if(ws !== null && ws.isopen)
+    // if we have an open port, and have bytes to send downstream,
+    if (ws !== null && ws.readyState === 1) {
+      // no buffering
+      if (dtin.io()) {
+        let arr = dtin.get()
+        if(debug) console.log('WS transmission as array', arr)
+        let bytesOut = Uint8Array.from(arr)
+        // HERE insertion -> buffer.from() ?
+        if(debug) console.log("WS sending buffer", bytesOut.buffer)
+        ws.send(bytesOut.buffer)
+      }
+    }
+
+    // check if we have outgoing to pass along
+    if (this.outbuffer.length > 0 && !dtout.io()) {
+      dtout.put(this.outbuffer.shift())
+    }
+
+  }
+}
+
+export default VFPT
diff --git a/hunks/comm/websocketclient.js b/hunks/pipes/websocketclient.js
similarity index 100%
rename from hunks/comm/websocketclient.js
rename to hunks/pipes/websocketclient.js
diff --git a/pipes/pipetemplate.js b/pipes/pipetemplate.js
new file mode 100644
index 0000000000000000000000000000000000000000..913ae0271e9d5709c8f67cef1e3e7b6f92f8c314
--- /dev/null
+++ b/pipes/pipetemplate.js
@@ -0,0 +1,5 @@
+const {
+  parentPort
+} = require('worker_threads')
+
+parentPort.postMessage('hello worker')
diff --git a/scratch/test.png b/scratch/test.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d416e2339529d7e07a8fd2aeb97b6746bf65eb9
Binary files /dev/null and b/scratch/test.png differ