ilabs-flir 2.3.8 → 2.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import android.util.Log;
8
8
  import com.flir.thermalsdk.ErrorCode;
9
9
  import com.flir.thermalsdk.androidsdk.ThermalSdkAndroid;
10
10
  import com.flir.thermalsdk.androidsdk.image.BitmapAndroid;
11
+ import com.flir.thermalsdk.image.ImageBuffer;
11
12
  import com.flir.thermalsdk.image.Palette;
12
13
  import com.flir.thermalsdk.image.PaletteManager;
13
14
  import com.flir.thermalsdk.image.Point;
@@ -362,32 +363,37 @@ public class FlirSdkManager {
362
363
  // 1. Apply Palette
363
364
  if (paletteToApply != null) {
364
365
  try {
365
- Palette palette = null;
366
- // Standard FLIR palette list from samples
367
- String[] paletteNames = {"Gray", "Iron", "Rainbow", "Arctic", "Lava", "Coldest", "Hottest", "Wheel"};
366
+ List<Palette> sdkPalettes = PaletteManager.getDefaultPalettes();
368
367
 
369
- for (String name : paletteNames) {
370
- if (name.equalsIgnoreCase(paletteToApply)) {
371
- try {
372
- // We still need to find the Palette object from the SDK if possible
373
- // But we try to do it safely
374
- List<Palette> sdkPalettes = PaletteManager.getDefaultPalettes();
375
- for (Palette p : sdkPalettes) {
376
- if (p.name.equalsIgnoreCase(name)) {
377
- palette = p;
378
- break;
379
- }
368
+ if (paletteToApply.equalsIgnoreCase("Gray") || paletteToApply.equalsIgnoreCase("grayscale")) {
369
+ // User wants Gray - map to WhiteHot which is the SDK's standard grayscale
370
+ for (Palette p : sdkPalettes) {
371
+ if (p.name.equalsIgnoreCase("WhiteHot") || p.name.equalsIgnoreCase("White hot")) {
372
+ thermalImage.setPalette(p);
373
+ break;
374
+ }
375
+ }
376
+ } else {
377
+ Palette palette = null;
378
+ for (Palette p : sdkPalettes) {
379
+ if (p.name.equalsIgnoreCase(paletteToApply)) {
380
+ palette = p;
381
+ break;
382
+ }
383
+ }
384
+
385
+ if (palette != null) {
386
+ thermalImage.setPalette(palette);
387
+ } else if (paletteToApply.equalsIgnoreCase("Wheel")) {
388
+ // Fallback for Wheel if not found - some SDKs use different names
389
+ for (Palette p : sdkPalettes) {
390
+ if (p.name.contains("Wheel") || p.name.contains("ColorWheel") || p.name.contains("Rainbow")) {
391
+ thermalImage.setPalette(p);
392
+ break;
380
393
  }
381
- } catch (Throwable t) {
382
- Log.w(TAG, "Dynamic palette lookup failed, using fallback mechanism");
383
394
  }
384
- break;
385
395
  }
386
396
  }
387
-
388
- if (palette != null) {
389
- thermalImage.setPalette(palette);
390
- }
391
397
  } catch (Throwable t) {
392
398
  Log.e(TAG, "Failed to apply palette: " + paletteToApply, t);
393
399
  }
@@ -396,8 +402,9 @@ public class FlirSdkManager {
396
402
  // 2. Save Radiometric Snapshot if requested
397
403
  if (snapshotPath != null) {
398
404
  try {
405
+ Log.i(TAG, "[SNAPSHOT] Attempting to save radiometric snapshot: " + snapshotPath);
399
406
  thermalImage.saveAs(snapshotPath);
400
- Log.i(TAG, "Radiometric snapshot saved to: " + snapshotPath);
407
+ Log.i(TAG, "[SNAPSHOT] ✅ Success: Radiometric snapshot saved");
401
408
  if (snapshotCallback != null) {
402
409
  snapshotCallback.onSnapshotSaved(snapshotPath);
403
410
  }
@@ -410,12 +417,22 @@ public class FlirSdkManager {
410
417
  }
411
418
 
412
419
  // 3. Generate Bitmap for display
413
- Bitmap bitmap = BitmapAndroid.createBitmap(streamer.getImage()).getBitMap();
414
- if (bitmap != null) {
415
- latestBitmap = bitmap;
416
- if (listener != null) {
417
- listener.onFrame(bitmap);
420
+ // We use streamer.getImage() to get the rendered image with palette applied.
421
+ try {
422
+ Bitmap newBitmap = BitmapAndroid.createBitmap(streamer.getImage()).getBitMap();
423
+ if (newBitmap != null) {
424
+ Bitmap oldBitmap = latestBitmap;
425
+ latestBitmap = newBitmap;
426
+ if (listener != null) {
427
+ listener.onFrame(newBitmap);
428
+ }
429
+ // Recycle old bitmap to prevent memory leak
430
+ if (oldBitmap != null && oldBitmap != newBitmap) {
431
+ oldBitmap.recycle();
432
+ }
418
433
  }
434
+ } catch (Exception e) {
435
+ Log.e(TAG, "Bitmap creation failed", e);
419
436
  }
420
437
  });
421
438
  }
@@ -191,7 +191,8 @@ import ThermalSDK
191
191
  NSLog("[FlirManager] Network camera detected — authenticating...")
192
192
 
193
193
  // Use UUID-based persistent certificate name (matches FLIR sample).
194
- // The camera has a bug where re-auth with a different name conflicts.
194
+ // The camera has a bug where re-auth with a different name can conflict,
195
+ // so we generate a UUID once and persist it in UserDefaults.
195
196
  let certName = self.getPersistentCertificateName()
196
197
  NSLog("[FlirManager] Using certificate name: \(certName)")
197
198
 
@@ -435,7 +436,7 @@ import ThermalSDK
435
436
  // Compiler says it is NOT optional here, so direct assignment.
436
437
  let value = spot.getValue()
437
438
  result = value.value
438
-
439
+
439
440
  try? measurements.remove(spot)
440
441
  }
441
442
  }
@@ -445,8 +446,6 @@ import ThermalSDK
445
446
  #endif
446
447
  }
447
448
 
448
-
449
-
450
449
  @objc public func getTemperatureAtNormalized(_ nx: Double, y: Double) -> Double {
451
450
  #if FLIR_ENABLED
452
451
  guard let streamer = streamer, _isStreaming else { return Double.nan }
@@ -458,10 +457,10 @@ import ThermalSDK
458
457
 
459
458
  // Map normalized (0.0 - 1.0) to actual sensor pixels
460
459
  let cx = max(0, min(Int(w) - 1, Int(nx * w)))
461
- let cy = max(0, min(Int(h) - 1, Int(y * h)))
460
+ let cy_fixed = max(0, min(Int(h) - 1, Int(y * h)))
462
461
 
463
462
  if let measurements = thermalImage.measurements,
464
- let spot = try? measurements.addSpot(CGPoint(x: cx, y: cy)) {
463
+ let spot = try? measurements.addSpot(CGPoint(x: cx, y: cy_fixed)) {
465
464
  result = spot.getValue().value
466
465
  try? measurements.remove(spot)
467
466
  }
@@ -503,8 +502,6 @@ import ThermalSDK
503
502
 
504
503
  // MARK: - Battery (stub - not needed per user)
505
504
 
506
- // MARK: - Battery (stub - not needed per user)
507
-
508
505
  @objc public func getBatteryLevel() -> Int { return -1 }
509
506
  @objc public func isBatteryCharging() -> Bool { return false }
510
507
 
@@ -531,21 +528,16 @@ import ThermalSDK
531
528
  }
532
529
 
533
530
  @objc public func getAvailablePalettes() -> [String] {
534
- return ["Gray", "Iron", "Rainbow", "Arctic", "Lava", "Coldest", "Hottest", "Wheel"]
531
+ return ["WhiteHot", "Iron", "Rainbow", "Arctic", "Lava", "Coldest", "Hottest", "Wheel"]
535
532
  }
536
533
 
537
534
  @objc public func generatePaletteIcons() -> [[String: String]] {
538
535
  let paletteNames = getAvailablePalettes()
539
- let fileManager = FileManager.default
540
- let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
541
- let paletteDir = cacheDir.appendingPathComponent("flir_palettes")
542
-
543
536
  var results: [[String: String]] = []
544
537
  for name in paletteNames {
545
- let iconURL = paletteDir.appendingPathComponent("\(name.lowercased()).png")
546
538
  results.append([
547
539
  "name": name,
548
- "uri": fileManager.fileExists(atPath: iconURL.path) ? iconURL.absoluteString : ""
540
+ "uri": "" // No URI - rely on local assets if any
549
541
  ])
550
542
  }
551
543
  return results
@@ -604,20 +596,15 @@ import ThermalSDK
604
596
  return newName
605
597
  }
606
598
 
607
- #if FLIR_ENABLED
608
- private func interfaceName(_ iface: FLIRCommunicationInterface) -> String {
609
- if iface.contains(.lightning) { return "LIGHTNING" }
610
- if iface.contains(.network) { return "NETWORK" }
611
- if iface.contains(.flirOneWireless) { return "WIRELESS" }
612
- if iface.contains(.emulator) { return "EMULATOR" }
599
+ private func interfaceName(_ iface: Int) -> String {
600
+ // Placeholder for interface name mapping
613
601
  return "UNKNOWN"
614
602
  }
615
- #endif
616
603
  }
617
604
 
605
+ #if FLIR_ENABLED
618
606
  // MARK: - Discovery Delegate
619
607
 
620
- #if FLIR_ENABLED
621
608
  extension FlirManager: FLIRDiscoveryEventDelegate {
622
609
  public func cameraDiscovered(_ camera: FLIRDiscoveredCamera) {
623
610
  let identity = camera.identity
@@ -632,7 +619,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
632
619
  let deviceInfo = FlirDeviceInfo(
633
620
  deviceId: deviceId,
634
621
  name: camera.displayName ?? deviceId,
635
- communicationType: interfaceName(identity.communicationInterface()),
622
+ communicationType: interfaceName(Int(identity.communicationInterface().rawValue)),
636
623
  isEmulator: identity.communicationInterface() == .emulator
637
624
  )
638
625
 
@@ -653,7 +640,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
653
640
  }
654
641
 
655
642
  public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
656
- NSLog("[FlirManager] Discovery finished: \(iface)")
643
+ NSLog("[FlirManager] Discovery finished: \(iface.rawValue)")
657
644
  }
658
645
 
659
646
  public func cameraLost(_ cameraIdentity: FLIRIdentity) {
@@ -669,11 +656,9 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
669
656
  }
670
657
  }
671
658
  }
672
- #endif
673
659
 
674
660
  // MARK: - Camera Delegate
675
661
 
676
- #if FLIR_ENABLED
677
662
  extension FlirManager: FLIRDataReceivedDelegate {
678
663
  public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
679
664
  NSLog("[FlirManager] Camera disconnected: \(error?.localizedDescription ?? "clean")")
@@ -690,11 +675,9 @@ extension FlirManager: FLIRDataReceivedDelegate {
690
675
  }
691
676
  }
692
677
  }
693
- #endif
694
678
 
695
679
  // MARK: - Stream Delegate
696
680
 
697
- #if FLIR_ENABLED
698
681
  extension FlirManager: FLIRStreamDelegate {
699
682
  public func onError(_ error: Error) {
700
683
  NSLog("[FlirManager] Stream error: \(error)")
@@ -702,13 +685,10 @@ extension FlirManager: FLIRStreamDelegate {
702
685
  }
703
686
 
704
687
  public func onImageReceived() {
705
- NSLog("[FLIR-TRACE 1️⃣] onImageReceived called on SDK thread")
706
-
707
688
  // Process frame on dedicated render queue (matches sample app pattern)
708
689
  // This prevents blocking the SDK callback thread and main thread
709
690
  // Guard to skip frame if already processing (prevents backpressure/latency)
710
691
  guard !_isProcessingFrame else {
711
- NSLog("[FLIR-TRACE ⏩] Skipping frame (already processing)")
712
692
  return
713
693
  }
714
694
 
@@ -716,12 +696,9 @@ extension FlirManager: FLIRStreamDelegate {
716
696
  renderQueue.async { [weak self] in
717
697
  defer { self?._isProcessingFrame = false }
718
698
  guard let self = self, self._isStreaming, let streamer = self.streamer else {
719
- NSLog("[FLIR-TRACE ❌] No self, streamer or not streaming in renderQueue")
720
699
  return
721
700
  }
722
701
 
723
- NSLog("[FLIR-TRACE 2️⃣] Processing on renderQueue")
724
-
725
702
  objc_sync_enter(self.stateLock)
726
703
  let currentStreamer = self.streamer
727
704
  let streaming = self._isStreaming
@@ -740,7 +717,26 @@ extension FlirManager: FLIRStreamDelegate {
740
717
 
741
718
  streamer.withThermalImage { thermalImage in
742
719
  // 1. Apply Palette
743
- if let palette = thermalImage.paletteManager.getDefaultPalettes().first(where: { $0.name.lowercased() == paletteToApply.lowercased() }) {
720
+ let sdkPalettes = thermalImage.paletteManager.getDefaultPalettes()
721
+ var targetPalette: FLIRPalette? = nil
722
+
723
+ if paletteToApply.lowercased() == "gray" || paletteToApply.lowercased() == "grayscale" {
724
+ // Map Gray to WhiteHot (standard SDK name)
725
+ targetPalette = sdkPalettes.first(where: {
726
+ $0.name.lowercased() == "whitehot" || $0.name.lowercased() == "white hot"
727
+ })
728
+ } else {
729
+ targetPalette = sdkPalettes.first(where: { $0.name.lowercased() == paletteToApply.lowercased() })
730
+
731
+ // Fallback for Wheel
732
+ if targetPalette == nil && paletteToApply.lowercased() == "wheel" {
733
+ targetPalette = sdkPalettes.first(where: {
734
+ $0.name.contains("Wheel") || $0.name.contains("ColorWheel") || $0.name.contains("Rainbow")
735
+ })
736
+ }
737
+ }
738
+
739
+ if let palette = targetPalette {
744
740
  thermalImage.palette = palette
745
741
  }
746
742
 
@@ -753,29 +749,23 @@ extension FlirManager: FLIRStreamDelegate {
753
749
  NSLog("[FlirManager] Failed to save radiometric snapshot: \(error)")
754
750
  }
755
751
  }
752
+
753
+ // 3. Generate UIImage for display
754
+ // Grab the image while the thermal image is locked to ensure settings are applied
755
+ if let image = streamer.getImage() {
756
+ self._latestImage = image
757
+ let width = Int(image.size.width)
758
+ let height = Int(image.size.height)
759
+
760
+ DispatchQueue.main.async { [weak self] in
761
+ self?.delegate?.onFrameReceived(image, width: width, height: height)
762
+ }
763
+ }
756
764
  }
757
-
758
- NSLog("[FLIR-TRACE 3️⃣] Streamer updated successfully")
759
765
  } catch {
760
- NSLog("[FLIR-TRACE ❌] Streamer update failed: \(error)")
766
+ NSLog("[FlirManager] Streamer update failed: \(error)")
761
767
  return
762
768
  }
763
-
764
- guard let image = streamer.getImage() else {
765
- NSLog("[FLIR-TRACE ❌] streamer.getImage() returned nil")
766
- return
767
- }
768
-
769
- NSLog("[FLIR-TRACE 4️⃣] Got image from streamer: \(image.size.width)x\(image.size.height)")
770
-
771
- self._latestImage = image
772
- let width = Int(image.size.width)
773
- let height = Int(image.size.height)
774
-
775
- DispatchQueue.main.async { [weak self] in
776
- NSLog("[FLIR-TRACE 5️⃣] Dispatching to delegate.onFrameReceived on main thread")
777
- self?.delegate?.onFrameReceived(image, width: width, height: height)
778
- }
779
769
  }
780
770
  }
781
771
  }
@@ -72,9 +72,6 @@
72
72
  }
73
73
 
74
74
  - (void)updateWithImage:(UIImage *)image {
75
- if (!image)
76
- return;
77
-
78
75
  dispatch_async(dispatch_get_main_queue(), ^{
79
76
  self.imageView.image = image;
80
77
  });
@@ -89,9 +89,6 @@ static FlirState *_sharedState = nil;
89
89
 
90
90
  - (void)updateFrame:(UIImage *)image
91
91
  withTemperatureData:(NSArray<NSNumber *> *)tempData {
92
- if (!image)
93
- return;
94
-
95
92
  dispatch_async(_accessQueue, ^{
96
93
  self.latestImage = image;
97
94
 
@@ -157,6 +154,15 @@ static FlirState *_sharedState = nil;
157
154
  self->_temperatureData = nil;
158
155
  self->_imageWidth = 0;
159
156
  self->_imageHeight = 0;
157
+
158
+ // Notify view to clear instantly
159
+ if (self.onTextureUpdate) {
160
+ dispatch_async(dispatch_get_main_queue(), ^{
161
+ if (self.onTextureUpdate) {
162
+ self.onTextureUpdate(nil, 7);
163
+ }
164
+ });
165
+ }
160
166
  });
161
167
  }
162
168
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.3.8",
3
+ "version": "2.3.10",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",