imagej / imagej-ops

ImageJ Ops: "Write once, run anywhere" image processing
https://imagej.net/libs/imagej-ops
BSD 2-Clause "Simplified" License
88 stars 42 forks source link

Improve usability of op built-in methods #502

Open ctrueden opened 7 years ago

ctrueden commented 7 years ago

We actually have four scenarios:

  1. Run an op type-safely.
  2. Run an op type-flexibly.
  3. Match an op type-safely.
  4. Match an op type-flexibly.

Right now, the built-in method signatures cover case (1) above. And the OpService's op method covers (4). We also partially/limitedly cover (3) and (4) via the Functions, Computers, Inplaces and Hybrids utility class methods.

I want to improve the API and/or architecture to make all four scenarios above easy.


What follows are some now-stale notes I wrote while thinking about this problem. I have other thoughts too, but no time to write them down at this moment. Please note that the ideas below do not necessarily represent current thinking—I just want to keep them preserved somewhere so they are not totally lost. I will return to this issue as I stumble across other ideas, topic branches, etc. The gist of this issue is simply to fulfill the requirements outlined above.. somehow!


//
// 1. Run an op type-safely.
// Need to ensure there are aliases for all op namespaces.
//

// @CreateKernelNamespace kernel
k = kernel.gauss(...typed...)

// -- LONGER --
// @CreateNamespace create
k = create.kernel().gauss(...typed...)

// -- LONGEST --
// @OpService ops
k = ops().create().kernel().gauss(...typed...)

//
// 2. Run an op type-flexibly.
// A new interface hierarchy?
//

// @Ops.Create.Kernel kernel
k = kernel.gauss(...any...)

// -- LONGER --

// @Ops.Create create
k = create.kernel().gauss(...any...)

// -- LONGEST --

// @OpService ops
k = ops.create().kernel().gauss(...any...)

//
// 3. Match an op type-safely.
// A new interface hierarchy?
//

// @Ops ops
// @Ops.Create create
// @Ops.Create.Kernel kernel

op = kernel.gaussOp(...any...)
// also OK, but longer:
op = create.kernel().gaussOp(...any...)
// and longer still:
op = ops.create().kernel().gaussOp(...any...)

//
// 4. Match an op type-flexibly.
//

k = gauss.run(...any...)
// also OK, but longer:
k = kernel.gauss.run(...typed...)
// and longer still:

Idea from @gab1one:

# @OpService ops
# @OpGetService opgs
# @OpRunService oprs

img = ops.create().img(300,300)

add = opgs.math().add(int, int)

out = oprs.map(add, img)
out = ops.map().map(add, img)
ctrueden commented 7 years ago

Some more old notes, this time about Java:

// TODO: consider whether to divorce OpService and OpEnvironment
// into separate objects. This might simplify the OpService a lot.

OpsTypeSafeRunTree typeSafeRun

net.imagej.ops.Ops
 - autogenerated
 - all sub classes autogenerated
 - could put execution shortcuts in here

@Parameter
net.imagej.ops.Ops.Math math;

Ops.Math ops.math()

///////////////////////////////////////////////////////////////////
net.imagej.ops.<something>.OpMethods opm = new OpMethods(ops())

@Parameter
OpService ops;

@Parameter
MathNamespace math;

@Parameter
net.imagej.ops.math.MathMethods math;

MathMethods mm = opms.math();
///////////////////

class MyAwesomeOp ... {

  @Parameter
  private MathOps math;

  @Parameter
  private GlobalOps global;

  @Parameter
  private FeatureOps feature;

  @Parameter
  private TypesafeOps tops;

  run() {
    Img result = tops.math().add(stuff, things)
  }
}

///////////////////

OpService ops;
OpRunService ors;

OpRunService ors = ops.safe()

ops.safe().math().add(A, B, C)
ops.math().add(Object...)

ops.stuff...

typeSafeRunner = new OpTypeSafeRunTree(ops)
Img result = typeSafeRunner.math().add(...Img, Img)

OpRunService ors

OpMatchService oms
 - mathOp = oms.math().add(...)
 - oms.c2(Ops.Math.Add.class, ...)
 - oms.f2(Ops.Math.Add.class, ...)

TypedOpEnvironment // depends on ALL
Each typed NS depends on the stuff in its own package
MathNamespace

@Parameter
TypedMathNS math;

math.add(Img, Img)

//////////////////////////////////////////////

Ops.Math.Add.class
///////////////

Img<T> img1, img2;
typeSafeRun.math().add(img1, img2) // Does not compile

RAI = math.add(RAI, RAI)
II = math.add(II, RAI)

RAI result = math.add(RAI, RAI)

MathNamespace.add(RAI, RAI)
MathNamespace.add(II, II)

result = typeSafeRun.create().kernel().gauss(new double[] {...})

gaussOp = typeSafeMatch.create().kernel().gauss(new double[] {...})

gaussOp = easyMatch.create().kernel().gauss(someCrazyObjectWhichCanBeConvertedToDouble[])

ij.op().math().add(...) <-- make this type flexible with Object... always
// we no longer have ALL core op implementations "polluting" the base package

openvironment.math() -> MathDynamic

@Parameter
MathTyped math;

math.add(img, img) ==== math.addOp(img, img).run()

net.imagej.ops.gateway.OpGateway
- all namespaces?
- pointers to all namespaces

-----

//
// 1. Run an op type-safely.
// Keep the namespace stuff, but use "Ops" suffix instead of "Namespace"?
//

@Parameter
private CreateKernelOps kernel;

@Override
public void run() {
  k = kernel.gauss(...typed...)
}

// -- LONGER --

@Parameter
private CreateOps create;

@Override
public void run() {
  k = create.kernel().gauss(...typed...)
}

// -- LONGEST --

@Parameter
private OpService ops;

@Override
public void run() {
  k = ops().create().kernel().gauss(...typed...)
}

-----

//
// 2. Run an op type-flexibly.
// A new interface hierarchy?
//

@Parameter
private Ops.Create.Kernel kernel;

@Override
public void run() {
  k = kernel.gauss(...any...)
}

// -- LONGER --

@Parameter
private Ops.Create create;

@Override
public void run() {
  k = create.kernel().gauss(...typed...)
}

// -- LONGEST --

@Parameter
private OpService ops;

@Override
public void run() {
  k = ops().create().kernel().gauss(...typed...)
}

-----

//
// 3. Match an op type-safely.
// A new interface hierarchy?
//

// @Ops ops
// @Ops.Create create
// @Ops.Create.Kernel kernel
// @Ops.Create.Kernel.Gauss gaussOp

// But this gaussOp is _not_ an Op! Confusing... it only has a run(Object...) method. So it will match the appropriate op. ???

-----

//
// 4. Match an op type-flexibly.
//

k = gauss.run(...any...)
// also OK, but longer:
k = kernel.gauss.run(...typed...)
// and longer still: