diff --git a/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/aggregators/CenterContentAggregator.kt b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/aggregators/CenterContentAggregator.kt new file mode 100644 index 00000000..1bc8fcb0 --- /dev/null +++ b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/aggregators/CenterContentAggregator.kt @@ -0,0 +1,46 @@ +package org.vitrivr.engine.index.aggregators + +import org.vitrivr.engine.core.context.IndexContext +import org.vitrivr.engine.core.model.content.element.AudioContent +import org.vitrivr.engine.core.model.content.element.ContentElement +import org.vitrivr.engine.core.model.content.element.ImageContent +import org.vitrivr.engine.core.model.content.element.TextContent +import org.vitrivr.engine.core.model.retrievable.Retrievable +import org.vitrivr.engine.core.operators.Operator +import org.vitrivr.engine.core.operators.ingest.Aggregator +import org.vitrivr.engine.core.operators.ingest.AggregatorFactory +import org.vitrivr.engine.core.operators.ingest.Segmenter + +/** + * A [Aggregator] that returns the middle [ContentElement] of each type. + * + * @version 1.0.0 + */ +class CenterContentAggregator : AggregatorFactory { + + /** + * Returns an [CenterContentAggregator.Instance]. + * + * @param input The [Segmenter] to use as input. + * @param context The [IndexContext] to use. + * @param parameters Optional set of parameters. + * @return [AllContentAggregator.Instance] + */ + override fun newOperator(input: Segmenter, context: IndexContext, parameters: Map): Aggregator = Instance(input, context) + + /** + * The [Instance] returns by the [AggregatorFactory] + */ + private class Instance(override val input: Operator, context: IndexContext) : AbstractAggregator(input, context) { + override fun aggregate(content: List>): List> { + val imageContent = content.filterIsInstance() + val audioContent = content.filterIsInstance() + val textContent = content.filterIsInstance() + return listOfNotNull( + imageContent.let { if (it.isNotEmpty()) it[it.size / 2] else null }, + audioContent.let { if (it.isNotEmpty()) it[it.size / 2] else null }, + textContent.let { if (it.isNotEmpty()) it[it.size / 2] else null } + ) + } + } +} \ No newline at end of file diff --git a/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/aggregators/image/RepresentativeImageContentAggregator.kt b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/aggregators/image/RepresentativeImageContentAggregator.kt index e0d42b9c..50c69d66 100644 --- a/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/aggregators/image/RepresentativeImageContentAggregator.kt +++ b/vitrivr-engine-index/src/main/kotlin/org/vitrivr/engine/index/aggregators/image/RepresentativeImageContentAggregator.kt @@ -1,6 +1,8 @@ package org.vitrivr.engine.index.aggregators.image import org.vitrivr.engine.core.context.IndexContext +import org.vitrivr.engine.core.model.color.MutableRGBFloatColorContainer +import org.vitrivr.engine.core.model.color.RGBByteColorContainer import org.vitrivr.engine.core.model.content.element.ContentElement import org.vitrivr.engine.core.model.content.element.ImageContent import org.vitrivr.engine.core.model.retrievable.Retrievable @@ -8,6 +10,7 @@ import org.vitrivr.engine.core.operators.Operator import org.vitrivr.engine.core.operators.ingest.Aggregator import org.vitrivr.engine.core.operators.ingest.AggregatorFactory import org.vitrivr.engine.core.operators.ingest.Segmenter +import org.vitrivr.engine.core.util.extension.getRGBArray import org.vitrivr.engine.index.aggregators.AbstractAggregator /** @@ -27,18 +30,52 @@ class RepresentativeImageContentAggregator : AggregatorFactory { * @param parameters Optional set of parameters. * @return [RepresentativeImageContentAggregator.Instance] */ - override fun newOperator(input: Segmenter, context: IndexContext, parameters: Map): Aggregator = Instance(input, context) + override fun newOperator(input: Segmenter, context: IndexContext, parameters: Map): Aggregator = + Instance(input, context) /** * The [Instance] returns by the [AggregatorFactory] */ - private class Instance(override val input: Operator, context: IndexContext) : AbstractAggregator(input, context) { + private class Instance(override val input: Operator, context: IndexContext) : + AbstractAggregator(input, context) { override fun aggregate(content: List>): List> { val images = content.filterIsInstance() if (images.isEmpty()) { return emptyList() } - TODO() + + if (images.size == 1) { + return images + } + + /* Compute average image. */ + val firstImage = images.first() + val height = firstImage.height + val width = firstImage.width + val colors = List(firstImage.width * firstImage.height) { MutableRGBFloatColorContainer() } + images.forEach { imageContent -> + require(imageContent.height == height && imageContent.width == width) { "Unable to aggregate images! All images must have same dimension." } + imageContent.content.getRGBArray().forEachIndexed { index, color -> + colors[index] += RGBByteColorContainer.fromRGB(color) + } + } + + /* normalize */ + val div = images.size.toFloat() + colors.forEach { it /= div } + + /* find image with smallest pixel-wise distance */ + val mostRepresentative = images.minBy { imageContent -> + + imageContent.content.getRGBArray().mapIndexed { index, color -> + RGBByteColorContainer.fromRGB(color).toFloatContainer().distanceTo(colors[index]) + }.sum() + + } + + return listOf(mostRepresentative) + + } } } \ No newline at end of file