Camerax Analyzer ImageProxy - Image Already Closed - kotlin

I am using CameraX (Beta) and using Analyzer with an executor to analyze the image and draw bounds/overlay on it. Based on the documentation, I need to close the image proxy to continue to get the images into the analyze function. When I add the imageProxy.close() call, it fails with Image Already closed error. What am I missing here?
Analyzer Code:
private val successListener = OnSuccessListener<List<DetectedObject>> { papers ->
isAnalyzing.set(false)
val rectPoints= mutableListOf<Rect>()
Log.d(TAG," overlayRef Info: ${overlayRef.get()}")
for (paper in papers) {
val bounds = paper.boundingBox
val paperId = paper.trackingId
rectPoints+=bounds
Log.d(TAG, "Successful Paper Analysis - Bounds of the paper: $bounds")
Log.d(TAG," Labels found on the paper: ${paper.labels}")
}
Log.d(TAG, "Invoking pointsRectListener for : $rectPoints")
pointsRectListener?.invoke(rectPoints)
}
private val failureListener = OnFailureListener { e ->
isAnalyzing.set(false)
Log.e(TAG, "Paper analysis failure.", e)
}
#SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy?.image ?: return
Log.d(TAG,"entered analysis..analysis in progress?$isAnalyzing.get()")
if (!isAnalyzing.get()){
isAnalyzing.set(true)
Log.d(TAG,"No other analysis in progress..so starting analysis now")
val currentTimestamp = System.currentTimeMillis()
if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {
currentTimestamp - lastAnalyzedTimestamp
analysisSizeListener?.invoke(Size(imageProxy.width, imageProxy.height))
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
objectDetector.process(image)
.addOnSuccessListener(successListener)
.addOnFailureListener(failureListener)
}
}
imageProxy.close()
}
Code where I am instantiating and binding to lifecycle
paperAnalyzer=ImageAnalysis.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setTargetRotation(rotation)
.build()
.also {
it.setAnalyzer(cameraExecutor, PaperAnalyzer( WeakReference(overlayView)).apply {
pointsRectListener = { rectPoints ->
overlayView.rectPoints = rectPoints
}
analysisSizeListener = {
updateOverlayTransform(overlayView, it)
}
}
)
}
cameraProvider.unbindAll()
try {
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, paperAnalyzer)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider())
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}

It seems like the logic you're trying to accomplish looks like this.
fun analyze(imageProxy) {
if (isAnalyzing) {
imageProxy.close() // 1
} else {
val image = imageInput.fromMediaImage(...)
objectDetector.process(image)
.addOnSuccessListener(OnSuccessListener { result ->
// Do something with the result
imageProxy.close() // 2
isAnalyzing.set(false)
})
.addOnFailureListener(OnFailureListener { exception ->
// Do something with the exception
imageProxy.close() // 3
isAnalyzing.set(false)
})
}
}
I might be wrong, but it looks like you're doing 1, but not 2 and 3. Could you update your code to follow this pattern and see if you're still encountering the issue.

Related

Kotlin Coroutines, decompiled code of a suspend function

I'm reading Kotlin Coroutines by Tutorials, Chapter 4 - Suspending functions here by Ray Wenderlich
However, I'm really struggling to understand the author's explanation of the decompiled code. Would someone be able to explain/describe the path of execution in the decompiled code as it pertains to suspend functions/continuations?
suspend fun getUserSuspend(userId: String): User {
delay(1000)
return User(userId, "Filip")
}
#Nullable
public static final Object getUserSuspend(
#NotNull String userId,
#NotNull Continuation var1) {
Object $continuation;
label28: {
if (var1 instanceof < undefinedtype >) {
$continuation = (<undefinedtype>)var1;
if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
break label28;
}
}
$continuation = new ContinuationImpl(var1) {
// $FF: synthetic field
Object result;
int label;
Object L $0;
#Nullable
public final Object invokeSuspend (#NotNull Object result) {
this.result = result;
this.label | = Integer.MIN_VALUE;
return MainKt.getUserSuspend((String)null, this);
}
};
}
Object var2 =((<undefinedtype>)$continuation).result;
Object var4 = IntrinsicsKt . getCOROUTINE_SUSPENDED ();
switch(((<undefinedtype>)$continuation).label) {
case 0:
if (var2 instanceof Failure) {
throw ((Failure) var2).exception;
}
((<undefinedtype>)$continuation).L$0 = userId;
((<undefinedtype>)$continuation).label = 1;
if (DelayKt.delay(1000L, (Continuation)$continuation) == var4) {
return var4;
}
break;
case 1:
userId = (String)((<undefinedtype>)$continuation).L$0;
if (var2 instanceof Failure) {
throw ((Failure) var2).exception;
}
break;
default:
throw new IllegalStateException ("call to ’resume’ before ’invoke’ with coroutine");
}
return new User (userId, "Filip");
}
I found this video particularly useful. I've linked the relevant section in the video here: https://youtu.be/YrrUCSi72E8?t=393
at roughly 6:33, where he talks about continuations and labels.
Essentially, when a coroutine is suspended, the continuation is used to save the coroutine state. Once the method is no longer suspended (e.g a result was returned, or the task is done etc.), the continuation's "resume" method recalls the method with the continuation.

how to only log one line error message not several error message for my code

there're several elements inside configTypeBuilderList, if the value in ruleAttributes not same as the destinationField in ConfigTypeBuilder, it will log the error
ruleCriteriaList.forEach { configRuleCriteria ->
validateConfigTypeBuilder(configRuleCriteria.configTypeBuilderList, ruleAttributesNames)
}
private fun validateConfigTypeBuilder(configTypeBuilderList: List<ConfigTypeBuilder>, ruleAttributes: List<String>) {
val missAttributeList: MutableList<String> = mutableListOf()
configTypeBuilderList.forEach { configTypeBuilder ->
if(configTypeBuilder!= null) {
if (ruleAttributes.firstOrNull { ruleAttribute -> ruleAttribute == configTypeBuilder.destinationField } == null) {
if(!ruleAttributes.contains(configTypeBuilder.destinationField)) {
missAttributeList.add(configTypeBuilder.destinationField)
}
logger.error("{} is wrong", configTypeBuilder.destinationField)
}
}
}
The problem is each time there's only one element(configTypeBuilderList) go into validateConfigTypeBuilder, so the logger shows like this
logger.error("field1 is wrong")
logger.error("field2 is wrong")
...
What I need is, how can I modify my code in order to do this?
logger.error("field1, field2, field3 are wrong")
Edit
I tried the first solution, but I stuck here, I still get the same error result, the reason is because each time there's only one "destinationField", how can I make the list have all the error field, and then log the error, can I use continue or something?
Here are a couple of alternatives:
Add them to a list and log later.
fun foo()
val incorrectItems = mutableListOf<Any>()
// Do some stuff
// on error:
incorrectItems.add(someIncorrectItem)
// Do more stuff
// log the accumulated errors:
logger.error("${incorrectItems.joinToString("")} are wrong")
}
Partition your list into valid and invalid values. Log the invalid ones and process the good ones.
fun foo(someList: List<MyClass>) {
val (goodItems, badItems) = someList.partition { it.isValid() }
// ...where isValid() is whatever code you need to check is OK.
if (badItems.isNotEmpty()) {
logger.error("${badItems.joinToString("")} are wrong")
}
// Do stuff with goodItems
}

RxJava2 Flowable that emits results of multiple network calls without using create?

I have a generic screen that subscribes to an RxJava2 flowable that returns a List. It then displays the content in the list.
I have a use case now though where I need to collect data from multiple endpoints, and emit data once some complete, and then emit data again once the remaining ones complete.
I'm doing this using Flowable.create() but I've seen a lot of posts saying that there's usually a better and safer way to do so than using create? I seem to believe that is the case since I need to subscribe to an observable within the observable which ideally I wouldn't want to do?
Because I subscribe within, I know the emitter can become cancelled within the observable while other network calls are completing so I've added checks to ensure it doesn't throw an error after its disposed which do work (at least in testing...) [I also just remembered I have the code available to dispose of the inner subscription if I kept it like this, when the outer is disposed]
The first 2 calls may be incredibly fast (or instant) which is why i want to emit the first result right away, and then the following 4 network calls which rely on that data may take time to process.
It looks roughly like this right now...
return Flowable.create<List<Object>>({ activeEmitter ->
Single.zip(
single1(),
single2(),
BiFunction { single1Result: Object, single2result: Object ->
if (single1result.something || single2Result.somethingElse) {
activeEmitter.onNext(function(single1result, single2result) //returns list
}
Single.zip(
single3(single1result),
single4(single2result),
single5(single1result),
single6(single2result),
Function4 { single3Result: Object,
single4Result: Object,
single5Result: Object,
single6Result: Object ->
ObjectHolder(single1Result, single2Result, single3Result, single4Result, single5Result, single6Result)
}
)
}
).flatMap { objectHolder ->
objects.flatMap { objectHolder ->
Single.just(parseObjects(objectHolder))
}
}.subscribeBy(
onError = { error ->
if (!activeEmitter.isCancelled) {
activeEmitter.onError(error)
}
},
onSuccess = { results ->
if (!activeEmitter.isCancelled) {
activeEmitter.onNext(results)
activeEmitter.onComplete()
}
}
)
}, BackpressureStrategy.BUFFER)
I can't figure out another way to return a Flowable that emits the results of multiple different network calls without doing it like this?
Is there a different/better way I can't find?
I worked this out given ctranxuan response. Posting so he can tweak/optimize and then I accept his answer
return Single.zip(single1(), single2(),
BiFunction { single1result: Object, single2result: Object ->
Pair(single1result, single2result)
}
).toFlowable()
.flatMap { single1AndSingle2 ->
if (isFirstLoad) {
createItemOrNull(single1AndSingle2.first, single1AndSingle2.second)?.let { result ->
Single.just(listOf(result)).mergeWith(proceedWithFinalNetworkCalls(single1AndSingle2))
}.orElse {
proceedWithFinalNetworkCalls(single1AndSingle2).toFlowable()
}
} else {
proceedWithFinalNetworkCalls(single1AndSingle2).toFlowable()
}
}.doOnComplete {
isFirstLoad = false
}
fun proceedWithFinalNetworkCalls(): Flowable<List> {
return Single.zip(
single3(single1result),
single4(single2result),
single5(single1result),
single6(single2result),
Function4 { single3Result: Object,
single4Result: Object,
single5Result: Object,
single6Result: Object ->
ObjectHolder(single1Result, single2Result, single3Result, single4Result, single5Result, single6Result)
}
)
Sorry, it's in Java but from what I've understood, something like that may be a possible solution?
public static void main(String[] args) {
final Single<String> single1 = single1().cache();
single1.map(List::of)
.mergeWith(single1.zipWith(single2(), Map::entry)
.flatMap(entry -> Single.zip(
single3(entry.getKey()),
single4(entry.getValue()),
single5(entry.getKey()),
single6(entry.getValue()),
(el3, el4, el5, el6) -> objectHolder(entry.getKey(), entry.getValue(), el3, el4, el5, el6))))
.subscribe(System.out::println,
System.err::println);
Flowable.timer(1, MINUTES) // Just to block the main thread for a while
.blockingSubscribe();
}
private static List<String> objectHolder(final String el1,
final String el2,
final String el3,
final String el4,
final String el5,
final String el6) {
return List.of(el1, el2, el3, el4, el5, el6);
}
static Single<String> single1() {
return Single.just("s1");
}
static Single<String> single2() {
return Single.just("s2");
}
static Single<String> single3(String value) {
return single("s3", value);
}
static Single<String> single4(String value) {
return single("s4", value);
}
static Single<String> single5(String value) {
return single("s5", value);
}
static Single<String> single6(String value) {
return single("s6", value);
}
static Single<String> single(String value1, String value2) {
return Single.just(value1).map(l -> l + "_" + value2);
}
This outputs:
[s1]
[s1, s2, s3_s1, s4_s2, s5_s1, s6_s2]

Is it possible to combine mapAreas from ArcGISOnline in one offline map on android?

I try to create an offline map using ArcGIS Runtime SDK for Android 100.5.0. I follow preplanned workflow according the guide https://developers.arcgis.com/android/latest/guide/take-map-offline-preplanned.htm. I create mapAreas in ArcGISOnline and try to download them from device. I want to get an offline map, which contains all mapAreas together like in app maps.me(on a big map you have downloaded regions with deeper detailing), but instead I am getting an offline map made from last downloaded area. So I created mapArea "Europe" with scale world - cities and mapArea "Berlin" with scale cities - buildings (both basemaps - openstreetmaps, no feature layers) and downloaded them successfully, see 2 tpk files in a folder, but mobile_map.mmpk and package.info files only contain data related to last loaded area. Is it possible at all to get what I want, combine tpk files in one map?
My code in Kotlin:
val portal = Portal("https://www.arcgis.com/", false)
val portalItem = PortalItem(portal, itemID)
val offlineMapTask = OfflineMapTask(portalItem)
//get all of the preplanned map areas in the web map
val mapAreasFuture = offlineMapTask.preplannedMapAreasAsync
mapAreasFuture.addDoneListener {
try {
// get the list of areas
val mapAreas = mapAreasFuture.get()
val directory = getDirectory()
prepareDir(directory)
// loop through the map areas
var i = 0
for (mapArea in mapAreas) {
mapArea.loadAsync()
mapArea.addDoneLoadingListener {
val downloadJob = offlineMapTask.downloadPreplannedOfflineMap(mapArea, directory)
downloadJob.start()
downloadJob.addJobDoneListener {
i++
if (i == mapAreas.size) {
val offlineMapPackage = MobileMapPackage(path)
offlineMapPackage.addDoneLoadingListener({
if (offlineMapPackage.getLoadStatus() === LoadStatus.LOADED) {
val mobileMap = offlineMapPackage.getMaps().get(0)
myCompletionFuncToShowMap(mobileMap)
} else {
println("PACKAGING FAILED")
}
})
offlineMapPackage.loadAsync()
}
}
}
}
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
I duplicated the question on community.esri.com and got an answer, thanks to Luke Smallwood from esri. So, yes, it is possible. The "Europe" map area has to be added as basemap layer if I set mapView.map to Berlin and also every map area has to be saved in its own directory.
Downloading areas:
val portal = Portal("https://www.arcgis.com/", false)
val portalItem = PortalItem(portal, itemID)
val offlineMapTask = OfflineMapTask(portalItem)
//get all of the preplanned map areas in the web map
val mapAreasFuture = offlineMapTask.preplannedMapAreasAsync
mapAreasFuture.addDoneListener {
try {
// get the list of areas
val mapAreas = mapAreasFuture.get()
val directory = getDirectory() //my function returns String path
// loop through the map areas
for (mapArea in mapAreas) {
mapArea.loadAsync()
mapArea.addDoneLoadingListener {
val downloadJob = offlineMapTask.downloadPreplannedOfflineMap(mapArea, directory + "/" + mapArea.portalItem.title)
downloadJob.start()
downloadJob.addJobDoneListener {
val offlineMapPackage = MobileMapPackage(directory + "/" + mapArea.portalItem.title)
offlineMapPackage.addDoneLoadingListener({
if (offlineMapPackage.getLoadStatus() != LoadStatus.LOADED) {
println("PACKAGING FAILED")
}
})
offlineMapPackage.loadAsync()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Files after downloading:
.../files/maps/Berlin/p13/129294b8-3a70-4d79-a421-24ff14cb19fc.tpk
.../files/maps/Berlin/p13/mobile_map.mmap
.../files/maps/Berlin/package.info
.../files/maps/Europa/p13/2547a985-c98f-49be-a187-5ae3b7a9da09.tpk
.../files/maps/Europa/p13/mobile_map.mmap
.../files/maps/Europa/package.info
Displaying an offline map:
val path = getDirectory() + "/Berlin"
val offlineMapPackage = MobileMapPackage(path)
offlineMapPackage.addDoneLoadingListener {
if (offlineMapPackage.getLoadStatus() == LoadStatus.LOADED && !offlineMapPackage.getMaps().isEmpty()) {
mapView.map = offlineMapPackage.getMaps().get(0)
val cache = TileCache(getDirectory() + "/Europa/p13/2547a985-c98f-49be-a187-5ae3b7a9da09.tpk")
val layer = ArcGISTiledLayer(cache)
mapView.map.basemap.baseLayers.add(layer)
mapView.map.minScale = 1.8489297737236E7
mapView.map.maxScale = 2256.994353
} else {
println("NO MAP FILES")
}
}
offlineMapPackage.loadAsync()
It is important to adjust mapView.map.minScale to include Europe levels, otherwise on device the map only allows to scale between Berlin levels. The scale levels are listed here https://www.esri.com/arcgis-blog/products/product/mapping/web-map-zoom-levels-updated/ .

"matchMedia" support in Dart

How to use window.matchMedia in Dart?
I have found corresponding method:
MediaQueryList matchMedia(String query)
And "MediaQueryList" method:
void addListener(MediaQueryListListener listener)
But: MediaQueryListListener has no constructor and looks like some sort of a generated stub.
I have JS example:
var mq = window.matchMedia( "(min-width: 500px)" );
// media query event handler
if (matchMedia) {
var mq = window.matchMedia("(min-width: 500px)");
mq.addListener(WidthChange);
WidthChange(mq);
}
// media query change
function WidthChange(mq) {
if (mq.matches) {
// window width is at least 500px
}
else {
// window width is less than 500px
}
}
And it has good support http://caniuse.com/#feat=matchmedia
As pointed in a comment it doesn't seem to be implemented in Dart for now.
However you can use dart:js to do that like this :
import 'dart:js';
main() {
if (context['matchMedia'] != null) {
final mq = context.callMethod('matchMedia', ['(min-width: 500px)']);
mq.callMethod('addListener', [widthChange]);
widthChange(mq);
}
}
widthChange(mq) {
if (mq['matches']) {
print('window width is at least 500px');
} else {
print('window width is less than 500px');
}
}