Skip to content

Commit

Permalink
generalising field of view; #7
Browse files Browse the repository at this point in the history
  • Loading branch information
vreuter committed Jul 29, 2024
1 parent 5138d47 commit 4490e8d
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 72 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ lazy val compileSettings = Def.settings(
"utf8",
"-feature",
"-language:existentials",
"-explain",
// https://contributors.scala-lang.org/t/for-comprehension-requires-withfilter-to-destructure-tuples/5953
"-source:future", // for tuples in for comprehension; see above link
"-unchecked",
Expand Down
2 changes: 1 addition & 1 deletion modules/imaging/src/main/scala/ImagingContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package at.ac.oeaw.imba.gerlich.gerlib.imaging

/** Context in which an image was captured */
final case class ImagingContext(
fov: FieldOfView,
fov: FieldOfViewLike,
timepoint: ImagingTimepoint,
channel: ImagingChannel
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package at.ac.oeaw.imba.gerlich.gerlib.imaging
package instances

import cats.syntax.all.*

import at.ac.oeaw.imba.gerlich.gerlib.SimpleShow
import at.ac.oeaw.imba.gerlich.gerlib.numeric.NonnegativeInt
import at.ac.oeaw.imba.gerlich.gerlib.numeric.instances.nonnegativeInt.given
import at.ac.oeaw.imba.gerlich.gerlib.syntax.all.*

/** Typeclass instances for types related to representation of imaging field of
* view
*/
trait FieldOfViewLikeInstances:
/** Simply show a field of view by the text representation of the underlying
* integer value.
*/
given SimpleShow[FieldOfView] =
summon[SimpleShow[NonnegativeInt]].contramap(_.get)

/** Simply show a position name by the underlying value. */
given SimpleShow[PositionName] = SimpleShow.fromToString

/** Simply show a general field-of-view-like by distinguishing among the
* subtypes and choosing the appropriate instance.
*/
given simpleShowForFovLike(using
showFov: SimpleShow[FieldOfView],
showPos: SimpleShow[PositionName]
): SimpleShow[FieldOfViewLike] with
override def show_(fovLike: FieldOfViewLike): String = fovLike match {
case fov: FieldOfView => fov.show_
case pos: PositionName => pos.show_
}
end FieldOfViewLikeInstances

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package at.ac.oeaw.imba.gerlich.gerlib.imaging
package instances

package object fieldOfView extends FieldOfViewInstances
package object fieldOfViewLike extends FieldOfViewLikeInstances
5 changes: 2 additions & 3 deletions modules/imaging/src/main/scala/instances/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package object instances:
object all extends AllInstances

trait AllInstances
extends FieldOfViewInstances,
extends FieldOfViewLikeInstances,
ImagingChannelInstances,
ImagingTimepointInstances,
PositionNameInstances
ImagingTimepointInstances

This file was deleted.

41 changes: 26 additions & 15 deletions modules/imaging/src/main/scala/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package at.ac.oeaw.imba.gerlich.gerlib

import cats.*
import cats.derived.*
import cats.syntax.all.*

import io.github.iltotore.iron.{ :|, refineEither }
import io.github.iltotore.iron.{:|, refineEither}
import io.github.iltotore.iron.constraint.any.{Not, StrictEqual}
import io.github.iltotore.iron.constraint.char.{Digit, Letter}
import io.github.iltotore.iron.constraint.collection.{Empty, ForAll}
Expand All @@ -13,23 +14,28 @@ import at.ac.oeaw.imba.gerlich.gerlib.numeric.instances.nonnegativeInt.given

/** Tools and types related to imaging */
package object imaging:
private type Parser[Result] = String => Either[String, Result]

/** A field of view is a value of one of a relatively small set of types. */
type FieldOfViewLike = PositionName | FieldOfView
sealed trait FieldOfViewLike

/** Typeclass for which an instance provides a way to get a FOV-like value
* from a value of another type
*/
trait AdmitsFieldOfView[A, B <: FieldOfViewLike]:
def getFieldOfView: A => B
/** Helpers for working with FOV-like values */
object FieldOfViewLike:
def parse: Parser[FieldOfViewLike] =
s => FieldOfView.parse(s).leftFlatMap{
e1 => PositionName.parse(s).leftMap{
e2 => s"Could not parse FOV-like. Message 1: $e1. Message 2: $e2"
}
}

/** Type wrapper around 0-based index of field of view (FOV) */
final case class FieldOfView(private[imaging] get: NonnegativeInt)
derives Order
extends FieldOfViewLike derives Order

/** Helpers for working with fields of view */
object FieldOfView:
/** Wrap the given value as a field of view, if it's valid as one. */
def parse: String => Either[String, FieldOfView] =
def parse: Parser[FieldOfView] =
parseThroughNonnegativeInt("FieldOfView")(FieldOfView.apply)
end FieldOfView

Expand All @@ -46,21 +52,26 @@ package object imaging:
/** The name of a position / field of view is a string whose characters all
* fulfill the constraint.
*/
opaque type PositionName = String :| PositionNameConstraint
final case class PositionName(
private[imaging] get: String :| PositionNameConstraint
) extends FieldOfViewLike

/** Put instances here since the refinement is opaque, so underlying String won't be visible elsewhere. */
/** Put instances here since the refinement is opaque, so underlying String
* won't be visible elsewhere.
*/
object PositionName:
/** Refine through [[scala.util.Either]] as the monadic type. */
def parse: String => Either[String, PositionName] = _.refineEither
def parse: Parser[PositionName] =
_.refineEither[PositionNameConstraint].map(PositionName.apply)

/** Ordering is by natural (lexicographical) text ordering */
given Order[PositionName] = Order.by{ s => s: String }
given Order[PositionName] = Order.by(_.get: String)

/** Show the value as its simple text representation. */
given Show[PositionName] = Show.show{ s => s: String }
given Show[PositionName] = Show.show(_.get: String)

/** Show the value as its simple text representation. */
given SimpleShow[PositionName] = SimpleShow.instance{ s => s: String }
given SimpleShow[PositionName] = SimpleShow.instance(_.get: String)
end PositionName

end imaging
24 changes: 19 additions & 5 deletions modules/io/src/main/scala/csv/instances/InstancesForImaging.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ import at.ac.oeaw.imba.gerlich.gerlib.imaging.*
import at.ac.oeaw.imba.gerlich.gerlib.imaging.instances.all.given
import at.ac.oeaw.imba.gerlich.gerlib.syntax.all.*

/** Typeclass instances for types in the [[at.ac.oeaw.imba.gerlich.gerlib.imaging]] package */
/** Typeclass instances for types in the
* [[at.ac.oeaw.imba.gerlich.gerlib.imaging]] package
*/
trait InstancesForImaging:
/** Decode a field-of-view-like by first trying by integer, then by name. */
given CellDecoder[FieldOfViewLike] = liftToCellDecoder(FieldOfViewLike.parse)

given CellEncoder[FieldOfViewLike] with
override def apply(fovLike: FieldOfViewLike): String = fovLike.show_

/** Decode a CSV field/cell by using the companion object's parse function. */
given CellDecoder[FieldOfView] = liftToCellDecoder(FieldOfView.parse)

Expand All @@ -23,7 +31,7 @@ trait InstancesForImaging:
/** For CSV write, show the FOV just by the numeric value. */
given CellEncoder[PositionName] with
override def apply(cell: PositionName): String = cell.show_

/** Decode a CSV field/cell by using the companion object's parse function. */
given CellDecoder[ImagingChannel] = liftToCellDecoder(ImagingChannel.parse)

Expand All @@ -32,7 +40,9 @@ trait InstancesForImaging:
override def apply(cell: ImagingChannel): String = cell.show_

/** Decode a CSV field/cell by using the companion object's parse function. */
given CellDecoder[ImagingTimepoint] = liftToCellDecoder(ImagingTimepoint.parse)
given CellDecoder[ImagingTimepoint] = liftToCellDecoder(
ImagingTimepoint.parse
)

/** For CSV write, show the timepoint just by the numeric value. */
given CellEncoder[ImagingTimepoint] with
Expand All @@ -42,13 +52,17 @@ trait InstancesForImaging:
object InstancesForImaging:
private def nuclearDesignationKey: String = "nuclusNumber"

/** Get a "row" decoder that operates on a single item, to combine with other(s). */
/** Get a "row" decoder that operates on a single item, to combine with
* other(s).
*/
def singletonRowDecoderForNuclearDesignation(using
CellDecoder[NuclearDesignation]
): CsvRowDecoder[NuclearDesignation, String] =
getCsvRowDecoderForSingleton(nuclearDesignationKey)

/** Get a "row" encoder that operates on a single item, to combine with other(s). */
/** Get a "row" encoder that operates on a single item, to combine with
* other(s).
*/
def singletonRowEncoderForNuclearDesignation(using
CellEncoder[NuclearDesignation]
): CsvRowEncoder[NuclearDesignation, String] =
Expand Down
43 changes: 19 additions & 24 deletions modules/io/src/main/scala/csv/instances/InstancesForRoi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import at.ac.oeaw.imba.gerlich.gerlib.roi.*
import at.ac.oeaw.imba.gerlich.gerlib.roi.measurement.*

trait InstancesForRoi:
private type Header = String

/** The keys / column names of input files which correspond to the fields of
* the detected spot case class
Expand Down Expand Up @@ -57,18 +56,18 @@ trait InstancesForRoi:
): CellEncoder[MeanIntensity] = enc.contramap(_.toDouble)

/** Parse the detected spot from CSV field-by-field. */
given csvRowDecoderForDetectedSpot[C: CellDecoder](using
CellDecoder[FieldOfView],
given csvRowDecoderForDetectedSpot[C](using
CellDecoder[FieldOfViewLike],
CellDecoder[ImagingTimepoint],
CellDecoder[ImagingChannel],
CellDecoder[ZCoordinate[C]],
CellDecoder[YCoordinate[C]],
CellDecoder[XCoordinate[C]]
): CsvRowDecoder[DetectedSpot[C], Header] with
): CsvRowDecoder[DetectedSpot[C], String] with
override def apply(
row: RowF[Some, Header]
row: RowF[Some, String]
): DecoderResult[DetectedSpot[C]] =
val fovNel = row.as[FieldOfView](Key.FieldOfView.get).toValidatedNel
val fovNel = row.as[FieldOfViewLike](Key.FieldOfView.get).toValidatedNel
val timeNel = row.as[ImagingTimepoint](Key.Timepoint.get).toValidatedNel
val channelNel = row.as[ImagingChannel](Key.Channel.get).toValidatedNel
val zNel = row.as[ZCoordinate[C]](Key.CenterZ.get).toValidatedNel
Expand All @@ -95,25 +94,21 @@ trait InstancesForRoi:
* defined in this object.
*/
given csvRowEncoderForDetectedSpot[C: CellEncoder](using
CellEncoder[FieldOfView],
CellEncoder[ImagingTimepoint],
CellEncoder[ImagingChannel],
CellEncoder[ZCoordinate[C]],
CellEncoder[YCoordinate[C]],
CellEncoder[XCoordinate[C]]
): CsvRowEncoder[DetectedSpot[C], Header] with
override def apply(elem: DetectedSpot[C]): RowF[Some, Header] =
encFov: CellEncoder[FieldOfViewLike],
encTime: CellEncoder[ImagingTimepoint],
encCh: CellEncoder[ImagingChannel],
encZ: CellEncoder[ZCoordinate[C]],
encY: CellEncoder[YCoordinate[C]],
encX: CellEncoder[XCoordinate[C]]
): CsvRowEncoder[DetectedSpot[C], String] with
override def apply(elem: DetectedSpot[C]): RowF[Some, String] =
val kvs = NonEmptyList.of(
Key.FieldOfView.get -> summon[CellEncoder[FieldOfView]](
elem.fieldOfView
),
Key.Timepoint.get -> summon[CellEncoder[ImagingTimepoint]](
elem.timepoint
),
Key.Channel.get -> summon[CellEncoder[ImagingChannel]](elem.channel),
Key.CenterZ.get -> summon[CellEncoder[ZCoordinate[C]]](elem.centerZ),
Key.CenterY.get -> summon[CellEncoder[YCoordinate[C]]](elem.centerY),
Key.CenterX.get -> summon[CellEncoder[XCoordinate[C]]](elem.centerX),
Key.FieldOfView.get -> encFov(elem.fieldOfView),
Key.Timepoint.get -> encTime(elem.timepoint),
Key.Channel.get -> encCh(elem.channel),
Key.CenterZ.get -> encZ(elem.centerZ),
Key.CenterY.get -> encY(elem.centerY),
Key.CenterX.get -> encX(elem.centerX),
Key.Area.get -> summon[CellEncoder[Area]](elem.area),
Key.Intensity.get -> cellEncoderForMeanIntensity(
elem.intensity
Expand Down
2 changes: 1 addition & 1 deletion modules/roi/DetectedSpot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import at.ac.oeaw.imba.gerlich.gerlib.roi.measurement.{Area, MeanIntensity}
* spot
*/
final case class DetectedSpot[C](
fieldOfView: FieldOfView,
fieldOfView: FieldOfViewLike,
timepoint: ImagingTimepoint,
channel: ImagingChannel,
centerZ: ZCoordinate[C],
Expand Down

0 comments on commit 4490e8d

Please sign in to comment.