Skip to content

Commit

Permalink
Merge pull request #5 from bbsaclay/master
Browse files Browse the repository at this point in the history
Fixing brush feature
  • Loading branch information
bbsaclay authored Jul 13, 2021
2 parents e28c5c3 + 1683c14 commit 92ea3be
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 80 deletions.
8 changes: 4 additions & 4 deletions demos/segmentation-interactive/my-demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ class MyDemo extends LitElement {
<p class="icon" title="Add instance" @click=${() => this.element.mode = 'create'}>${createPencil}</p>
<p class="icon" title="Smart instance" @click=${() => this.element.mode = 'smart-create'}>${borderOuter}</p>
<p class="icon" title="Select" @click=${() => this.element.mode = 'select'}>${magicSelect}</p>
<p class="icon" title="Subtract" @click=${() => this.element.mode = 'edit-remove'}>${subtract}</p>
<p class="icon" title="Union" @click=${() => this.element.mode = 'edit-add'}>${union}</p>
<p class="icon" title="Subtract (Ctrl)" @click=${() => this.element.mode = 'edit-remove'}>${subtract}</p>
<p class="icon" title="Union (Shift)" @click=${() => this.element.mode = 'edit-add'}>${union}</p>
<p class="icon" title="Lock" @click=${() => this.element.mode = 'lock'}>${lock}</p>
<p class="icon" title="Zoom in" @click=${() => this.element.viewControls.zoomIn()}>${zoomIn}</p>
<p class="icon" title="Zoom out" @click=${() => this.element.viewControls.zoomOut()}>${zoomOut}</p>
<p class="icon" title="Zoom in (scroll)" @click=${() => this.element.viewControls.zoomIn()}>${zoomIn}</p>
<p class="icon" title="Zoom out (scroll)" @click=${() => this.element.viewControls.zoomOut()}>${zoomOut}</p>
</div>
</div>
`;
Expand Down
8 changes: 4 additions & 4 deletions demos/segmentation/my-demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ class MyDemo extends LitElement {
<p class="icon" title="Add instance" @click=${() => this.element.mode = 'create'}>${createPencil}</p>
<p class="icon" title="Add instance" @click=${() => this.element.mode = 'create-brush'}>${paintBrush}</p>
<p class="icon" title="Select" @click=${() => this.element.mode = 'select'}>${magicSelect}</p>
<p class="icon" title="Subtract" @click=${() => this.element.mode = 'edit-remove'}>${subtract}</p>
<p class="icon" title="Union" @click=${() => this.element.mode = 'edit-add'}>${union}</p>
<p class="icon" title="Subtract (Ctrl)" @click=${() => this.element.mode = 'edit-remove'}>${subtract}</p>
<p class="icon" title="Union (Shift)" @click=${() => this.element.mode = 'edit-add'}>${union}</p>
<p class="icon" title="Lock" @click=${() => this.element.mode = 'lock'}>${lock}</p>
<p class="icon" title="Zoom in" @click=${() => this.element.viewControls.zoomIn()}>${zoomIn}</p>
<p class="icon" title="Zoom out" @click=${() => this.element.viewControls.zoomOut()}>${zoomOut}</p>
<p class="icon" title="Zoom in (scroll)" @click=${() => this.element.viewControls.zoomIn()}>${zoomIn}</p>
<p class="icon" title="Zoom out (scroll)" @click=${() => this.element.viewControls.zoomOut()}>${zoomOut}</p>
</div>
</div>
`;
Expand Down
27 changes: 24 additions & 3 deletions packages/graphics-2d/src/controller-mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,9 @@ export class CreateBrushController extends Controller {
this.renderer.stage.removeListener('pointermove', this.onPointerMoveBrush);
this.renderer.stage.removeListener('pointerupoutside', this.onPointerUpBrush);
window.removeEventListener('keydown', this.onKeyDown, false);
this.roi.cacheAsBitmap = false;
this.roi.clear();
this.roi.cacheAsBitmap = true;
this.contours.visible = false;
}

Expand Down Expand Up @@ -285,10 +287,13 @@ export class CreateBrushController extends Controller {
* @param evt PIXIInteractionEvent
*/
onPointerDownBrush(evt: PIXIInteractionEvent) {
if (evt.data.button == 1) return;//middle button : nothing to do

this.isActive = true;
this.roi.x = this.renderer.mouse.x;
this.roi.y = this.renderer.mouse.y;


if (evt.data.button === 0 && !evt.data.originalEvent.ctrlKey && !evt.data.originalEvent.shiftKey) {
// create
this.editionMode = EditionMode.NEW_INSTANCE;
Expand All @@ -299,6 +304,7 @@ export class CreateBrushController extends Controller {
// remove
this.editionMode = EditionMode.REMOVE_FROM_INSTANCE;
}

const fillType = (this.editionMode === EditionMode.REMOVE_FROM_INSTANCE) ? 'remove' : 'add';
this.gmask.updateByMaskInRoi(this.roiMatrix,
[this.roi.x - this.roiRadius, this.roi.y - this.roiRadius, this.roi.x + this.roiRadius, this.roi.y + this.roiRadius],
Expand All @@ -312,15 +318,24 @@ export class CreateBrushController extends Controller {
*/
onPointerMoveBrush(evt: PIXIInteractionEvent) {
const newPos = this.renderer.getPosition(evt.data);
this.roi.x = newPos.x;
this.roi.y = newPos.y;
if (this.isActive) {
const fillType = (this.editionMode === EditionMode.REMOVE_FROM_INSTANCE) ? 'remove' : 'add';
this.gmask.updateByMaskInRoi(this.roiMatrix,
[this.roi.x - this.roiRadius, this.roi.y - this.roiRadius, this.roi.x + this.roiRadius, this.roi.y + this.roiRadius],
[newPos.x - this.roiRadius, newPos.y - this.roiRadius, newPos.x + this.roiRadius, newPos.y + this.roiRadius],
this.getTargetValue(), fillType
);
// filling space between successive mousepoints to enable easy surface painting
//... assuming roiMatrix is a circle
//... filling by a strait line even if the user describes a curve
const alpha = Math.atan2((newPos.y-this.roi.y),(newPos.x-this.roi.x));
const dy = Math.trunc(-Math.cos(alpha)*this.roiRadius);
const dx = Math.trunc(Math.sin(alpha)*this.roiRadius);
this.gmask.updateByPolygon([new Point(this.roi.x+dx,this.roi.y+dy),new Point(this.roi.x-dx,this.roi.y-dy),new Point(newPos.x-dx,newPos.y-dy),new Point(newPos.x+dx,newPos.y+dy)],
this.getTargetValue(), fillType
);
}
this.roi.x = newPos.x;
this.roi.y = newPos.y;
}

/**
Expand Down Expand Up @@ -599,6 +614,7 @@ export class MaskManager extends EventTarget {
this.gmask = gmask;
this.selectedId = { value: selectedId };
this.renderer.stage.addChild(this.contour);
// creating default controler
this.modes = {
'create': new CreatePolygonController({...this} as any),
'create-brush': new CreateBrushController({...this} as any),
Expand All @@ -610,6 +626,11 @@ export class MaskManager extends EventTarget {
this.modes[this.mode].activate();
}

/**
* Change the controler linked to the mode:
* @param mode string, the mode whome controler has to be changed
* @param controller the new controler
*/
public setController(mode: string, controller: Controller) {
if (mode === this.mode && this.modes[mode]) {
// remove active base controller
Expand Down
47 changes: 32 additions & 15 deletions packages/graphics-2d/src/graphic-mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,22 +297,33 @@ export class GraphicMask extends PIXIContainer {
* Update panoptic mask with a localised submask and a panoptic value
* @param mask binary flatten array
* @param box [l,t,r,b] search area
* @param newVal
* @param newVal new pixel/class value (corresponds to color for rendering)
*/
public updateByMaskInRoi(mask: Float32Array, box: [number, number, number, number],
newVal: [number, number, number], fillType: 'add' | 'remove' ='add') {
const pixels = this.ctx.getImageData(0,0,this.canvas.width, this.canvas.height);

//respect image boundaries
const width = box[2]-box[0];
const height = box[3]-box[1];
const roi = [(box[0]>0) ? box[0] : 0,
(box[1]>0) ? box[1] : 0,
(box[2]<this.canvas.width) ? box[2] : this.canvas.width,
(box[3]<this.canvas.height) ? box[3] : this.canvas.height];
const widthroi = roi[2]-roi[0];
const heightroi = roi[3]-roi[1];
const decx = (box[0]<0) ? -box[0] : 0;
const decy = (box[1]<0) ? -box[1] : 0;


const color = this.pixelToColor(...newVal);
const alpha = (Math.max(...color) === 0) ? 0 : MASK_ALPHA_VALUE;
const [id1, id2, cls] = newVal;
if (fillType === 'add') {
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const idx = (x + box[0] + (y + box[1]) * this.canvas.width);
for (let x = 0; x < widthroi; x++) {
for (let y = 0; y < heightroi; y++) {
const idx = (x + roi[0] + (y + roi[1]) * this.canvas.width);
const pixId = this.pixelId(idx);
if (mask[y * width + x] === 1 && !this.lockedInstances.has(fuseId(pixId))) {
if (mask[(y+decy)*width + (x+decx)] === 1 && !this.lockedInstances.has(fuseId(pixId))) {
this.orig!.data[4 * idx] = id1;
this.orig!.data[4 * idx + 1] = id2;
this.orig!.data[4 * idx + 2] = cls;
Expand All @@ -326,11 +337,11 @@ export class GraphicMask extends PIXIContainer {
}
} else if (fillType === 'remove') {
const fusedVal = fuseId(newVal);
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const idx = (x + box[0] + (y + box[1]) * this.canvas.width);
for (let x = 0; x < widthroi; x++) {
for (let y = 0; y < heightroi; y++) {
const idx = (x + roi[0] + (y + roi[1]) * this.canvas.width);
const pixId = this.pixelId(idx);
if (mask[y * width + x] === 1 && !this.lockedInstances.has(fuseId(pixId))
if (mask[(y+decy)*width + (x+decx)] === 1 && !this.lockedInstances.has(fuseId(pixId))
&& fuseId(pixId) == fusedVal) {
this.orig!.data[4 * idx] = 0;
this.orig!.data[4 * idx + 1] = 0;
Expand All @@ -350,13 +361,19 @@ export class GraphicMask extends PIXIContainer {

/**
* Update panoptic mask with a polygon to be filled with given panoptic value
* @param polygon
* @param id
* @param fillType
* @param polygon an array of points (points must be represented by integers)
* @param id new pixel/class value (corresponds to color for rendering)
* @param fillType 'add' or 'remove'
*/
public updateByPolygon(polygon: Point[], id: [number, number, number], fillType='add') {
public updateByPolygon(polygon: Point[], id: [number, number, number], fillType: 'add' | 'remove' ='add') {
const pixels = this.ctx.getImageData(0,0,this.canvas.width, this.canvas.height);
const [xMin, yMin, xMax, yMax] = getPolygonExtrema(polygon);
let [xMin, yMin, xMax, yMax] = getPolygonExtrema(polygon);
//respect image boundaries
if (xMin<0) xMin=0;
if (yMin<0) yMin=0;
if (xMax>this.canvas.width) xMax=this.canvas.width-1;
if (yMax>this.canvas.height) yMax=this.canvas.height-1;

if (this.lockedInstances.has(fuseId(id))) {
// do not update locked instances
return;
Expand Down
12 changes: 0 additions & 12 deletions packages/graphics-2d/src/pxn-canvas-2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,18 +282,6 @@ export class Canvas2d extends Canvas {
}
}

/**
* Snackbar temporary appearance
* To display mode instructions.
* @param text
*/
protected showTooltip(text: string) {
const x = this.shadowRoot!.getElementById("snackbar")!;
x.className = "show";
x.innerHTML = text;
setTimeout(() => { x.className = x.className.replace("show", ""); }, 3000);
}

/**
* Called on every property change
* @param changedProperty
Expand Down
69 changes: 27 additions & 42 deletions packages/graphics-2d/src/view-controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,58 +84,43 @@ export class ViewControls extends EventTarget {
* @param evt
*/
public onWheel(evt: WheelEvent) {
if (evt.ctrlKey) {
evt.preventDefault();
}
// Manipulate the scale based on direction
const distance = this.wheelDistance(evt) / 5 * this.viewer.s;
// if (this.s == this.smin && distance <= 0) {
// return;
// }
const so = this.viewer.s;
this.viewer.s += distance;
const x = evt.offsetX;
const y = evt.offsetY;

// Check to see that the scale is not outside of the specified bounds
if (this.viewer.s > this.viewer.smax) {
this.viewer.s = this.viewer.smax;
} else if (this.viewer.s < this.viewer.smin) {
// center placeholder if zoom is minimal
this.viewer.s = this.viewer.smin;
this.viewer.computeDrawableArea(this.viewer.canvasWidth, this.viewer.canvasHeight,
this.viewer.imageWidth, this.viewer.imageHeight, true);
this.viewer.sx = 0.5 * (1 - this.viewer.s) * this.viewer.rw;
this.viewer.sy = 0.5 * (1 - this.viewer.s) * this.viewer.rh;
}
this.viewer.sx = (this.viewer.sx - x) * (this.viewer.s / so) + x;
this.viewer.sy = (this.viewer.sy - y) * (this.viewer.s / so) + y;
this.viewer.stage.scale.set(this.viewer.s * this.viewer.rw / this.viewer.imageWidth,
this.viewer.s * this.viewer.rh / this.viewer.imageHeight);
this.viewer.stage.position.set(this.viewer.rx * this.viewer.s + this.viewer.sx, this.viewer.ry * this.viewer.s + this.viewer.sy);
this.triggerOnZoom();
this.computeHitArea();
if (evt.ctrlKey) evt.preventDefault();
const scaleFactor = 1.0+Math.sign(this.wheelDistance(evt))*0.1;
this.zoomFct(scaleFactor, [evt.x, evt.y]);
}

public zoomIn() {
this.viewer.s *= 1.1;
this.viewer.stage.scale.set(this.viewer.s * this.viewer.rw / this.viewer.imageWidth,
this.viewer.s * this.viewer.rh / this.viewer.imageHeight);
this.viewer.stage.position.set(this.viewer.rx * this.viewer.s + this.viewer.sx, this.viewer.ry * this.viewer.s + this.viewer.sy);
this.triggerOnZoom();
this.computeHitArea();
this.zoomFct(1.1, [this.viewer.rw/2, this.viewer.rh/2]);
}

public zoomOut() {
this.viewer.s *= 0.9;
if (this.viewer.s < this.viewer.smin) {
// center placeholder if zoom is minimal
this.zoomFct(0.9, [this.viewer.rw/2, this.viewer.rh/2]);
}

/**
* Generalized zoom function
* @param scaleFactor scale factor to be applied
* @param center point where the zoom will be centered
*/
public zoomFct(scaleFactor: number, center: [number, number]) {
// apply new scale
const oldscale = this.viewer.s;
this.viewer.s *= scaleFactor;
// Check to see that the scale is not outside of the specified bounds
if (this.viewer.s >= this.viewer.smax) {
this.viewer.s = this.viewer.smax;
} else if (this.viewer.s <= this.viewer.smin) {
this.viewer.s = this.viewer.smin;
// center placeholder if zoom is minimal
this.viewer.sx = 0.5 * (1 - this.viewer.s) * this.viewer.rw;
this.viewer.sy = 0.5 * (1 - this.viewer.s) * this.viewer.rh;
} else {
// apply zoom center (sx and sy are offsets)
this.viewer.sx = (this.viewer.sx - center[0]) * (this.viewer.s / oldscale) + center[0];
this.viewer.sy = (this.viewer.sy - center[1]) * (this.viewer.s / oldscale) + center[1];
}
this.viewer.stage.scale.set(this.viewer.s * this.viewer.rw / this.viewer.imageWidth,
this.viewer.s * this.viewer.rh / this.viewer.imageHeight);
// apply changes
this.viewer.stage.scale.set(this.viewer.s * this.viewer.rw / this.viewer.imageWidth, this.viewer.s * this.viewer.rh / this.viewer.imageHeight);
this.viewer.stage.position.set(this.viewer.rx * this.viewer.s + this.viewer.sx, this.viewer.ry * this.viewer.s + this.viewer.sy);
this.triggerOnZoom();
this.computeHitArea();
Expand Down

0 comments on commit 92ea3be

Please sign in to comment.