ilabs-flir 2.3.7 → 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;
@@ -108,12 +109,9 @@ public class FlirSdkManager {
108
109
 
109
110
  isInitialized = true;
110
111
  Log.i(TAG, "FLIR SDK initialized successfully. Arch: " + System.getProperty("os.arch"));
112
+ Log.d(TAG, "SDK initialized");
111
113
  } catch (Throwable e) {
112
114
  Log.e(TAG, "Critical failure during FLIR SDK initialization", e);
113
- }
114
- Log.d(TAG, "SDK initialized");
115
- } catch (Exception e) {
116
- Log.e(TAG, "SDK init failed", e);
117
115
  notifyError("SDK init failed: " + e.getMessage());
118
116
  }
119
117
  }
@@ -364,31 +362,38 @@ public class FlirSdkManager {
364
362
  streamer.withThermalImage(thermalImage -> {
365
363
  // 1. Apply Palette
366
364
  if (paletteToApply != null) {
367
- // Standard FLIR palette list from samples
368
- String[] paletteNames = {"Gray", "Iron", "Rainbow", "Arctic", "Lava", "Coldest", "Hottest", "Wheel"};
365
+ try {
366
+ List<Palette> sdkPalettes = PaletteManager.getDefaultPalettes();
369
367
 
370
- for (String name : paletteNames) {
371
- if (name.equalsIgnoreCase(paletteToApply)) {
372
- try {
373
- // We still need to find the Palette object from the SDK if possible
374
- // But we try to do it safely
375
- List<Palette> sdkPalettes = PaletteManager.getDefaultPalettes();
376
- for (Palette p : sdkPalettes) {
377
- if (p.name.equalsIgnoreCase(name)) {
378
- palette = p;
379
- break;
380
- }
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;
381
393
  }
382
- } catch (Throwable t) {
383
- Log.w(TAG, "Dynamic palette lookup failed, using fallback mechanism");
384
394
  }
385
- break;
386
395
  }
387
396
  }
388
-
389
- if (palette != null) {
390
- thermalImage.setPalette(palette);
391
- }
392
397
  } catch (Throwable t) {
393
398
  Log.e(TAG, "Failed to apply palette: " + paletteToApply, t);
394
399
  }
@@ -397,8 +402,9 @@ public class FlirSdkManager {
397
402
  // 2. Save Radiometric Snapshot if requested
398
403
  if (snapshotPath != null) {
399
404
  try {
405
+ Log.i(TAG, "[SNAPSHOT] Attempting to save radiometric snapshot: " + snapshotPath);
400
406
  thermalImage.saveAs(snapshotPath);
401
- Log.i(TAG, "Radiometric snapshot saved to: " + snapshotPath);
407
+ Log.i(TAG, "[SNAPSHOT] ✅ Success: Radiometric snapshot saved");
402
408
  if (snapshotCallback != null) {
403
409
  snapshotCallback.onSnapshotSaved(snapshotPath);
404
410
  }
@@ -411,12 +417,22 @@ public class FlirSdkManager {
411
417
  }
412
418
 
413
419
  // 3. Generate Bitmap for display
414
- Bitmap bitmap = BitmapAndroid.createBitmap(streamer.getImage()).getBitMap();
415
- if (bitmap != null) {
416
- latestBitmap = bitmap;
417
- if (listener != null) {
418
- 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
+ }
419
433
  }
434
+ } catch (Exception e) {
435
+ Log.e(TAG, "Bitmap creation failed", e);
420
436
  }
421
437
  });
422
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
 
@@ -520,72 +517,29 @@ import ThermalSDK
520
517
  }
521
518
 
522
519
  @objc public func setPaletteFromAcol(_ acol: Float) {
523
- // Map acol to dynamic palette names
524
520
  let palettes = getAvailablePalettes()
525
521
  let idx = Int(acol)
526
- if idx >= 0 && idx < palettes.count {
527
- setPalette(palettes[idx])
522
+ let maxEff = palettes.count
523
+ if maxEff > 0 {
524
+ let paletteIdx = idx % maxEff
525
+ let safeIdx = paletteIdx < 0 ? paletteIdx + maxEff : paletteIdx
526
+ setPalette(palettes[safeIdx])
528
527
  }
529
528
  }
530
529
 
531
530
  @objc public func getAvailablePalettes() -> [String] {
532
- #if FLIR_ENABLED
533
- if let streamer = streamer {
534
- var names: [String] = []
535
- streamer.withThermalImage { img in
536
- var palettes = img.paletteManager.getDefaultPalettes().map { $0.name }
537
- // Reorder: Move WhiteHot/Gray to the front
538
- if let grayIdx = palettes.firstIndex(where: { $0.lowercased() == "whitehot" || $0.lowercased() == "gray" }) {
539
- let gray = palettes.remove(at: grayIdx)
540
- palettes.insert(gray, at: 0)
541
- }
542
- names = palettes
543
- }
544
- if !names.isEmpty { return names }
545
- }
546
- #endif
547
- return ["WhiteHot", "iron", "rainbow", "arctic", "lava", "contrast", "hotcold", "medical"]
531
+ return ["WhiteHot", "Iron", "Rainbow", "Arctic", "Lava", "Coldest", "Hottest", "Wheel"]
548
532
  }
549
533
 
550
534
  @objc public func generatePaletteIcons() -> [[String: String]] {
535
+ let paletteNames = getAvailablePalettes()
551
536
  var results: [[String: String]] = []
552
- #if FLIR_ENABLED
553
- guard let streamer = streamer else { return [] }
554
-
555
- let fileManager = FileManager.default
556
- let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
557
- let paletteDir = cacheDir.appendingPathComponent("flir_palettes")
558
-
559
- if !fileManager.fileExists(atPath: paletteDir.path) {
560
- try? fileManager.createDirectory(at: paletteDir, withIntermediateDirectories: true)
537
+ for name in paletteNames {
538
+ results.append([
539
+ "name": name,
540
+ "uri": "" // No URI - rely on local assets if any
541
+ ])
561
542
  }
562
-
563
- streamer.withThermalImage { thermalImage in
564
- var palettes = thermalImage.paletteManager.getDefaultPalettes()
565
-
566
- // Reorder: Move WhiteHot/Gray to the front
567
- if let grayIdx = palettes.firstIndex(where: { $0.name.lowercased() == "whitehot" || $0.name.lowercased() == "gray" }) {
568
- let gray = palettes.remove(at: grayIdx)
569
- palettes.insert(gray, at: 0)
570
- }
571
-
572
- for palette in palettes {
573
- let iconURL = paletteDir.appendingPathComponent("\(palette.name.lowercased()).png")
574
- if !fileManager.fileExists(atPath: iconURL.path) {
575
- // Assuming palette.icon exists (common in FLIR SDK)
576
- if let icon = palette.icon {
577
- if let data = icon.pngData() {
578
- try? data.write(to: iconURL)
579
- }
580
- }
581
- }
582
- results.append([
583
- "name": palette.name,
584
- "uri": fileManager.fileExists(atPath: iconURL.path) ? iconURL.absoluteString : ""
585
- ])
586
- }
587
- }
588
- #endif
589
543
  return results
590
544
  }
591
545
 
@@ -642,20 +596,15 @@ import ThermalSDK
642
596
  return newName
643
597
  }
644
598
 
645
- #if FLIR_ENABLED
646
- private func interfaceName(_ iface: FLIRCommunicationInterface) -> String {
647
- if iface.contains(.lightning) { return "LIGHTNING" }
648
- if iface.contains(.network) { return "NETWORK" }
649
- if iface.contains(.flirOneWireless) { return "WIRELESS" }
650
- if iface.contains(.emulator) { return "EMULATOR" }
599
+ private func interfaceName(_ iface: Int) -> String {
600
+ // Placeholder for interface name mapping
651
601
  return "UNKNOWN"
652
602
  }
653
- #endif
654
603
  }
655
604
 
605
+ #if FLIR_ENABLED
656
606
  // MARK: - Discovery Delegate
657
607
 
658
- #if FLIR_ENABLED
659
608
  extension FlirManager: FLIRDiscoveryEventDelegate {
660
609
  public func cameraDiscovered(_ camera: FLIRDiscoveredCamera) {
661
610
  let identity = camera.identity
@@ -670,7 +619,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
670
619
  let deviceInfo = FlirDeviceInfo(
671
620
  deviceId: deviceId,
672
621
  name: camera.displayName ?? deviceId,
673
- communicationType: interfaceName(identity.communicationInterface()),
622
+ communicationType: interfaceName(Int(identity.communicationInterface().rawValue)),
674
623
  isEmulator: identity.communicationInterface() == .emulator
675
624
  )
676
625
 
@@ -691,7 +640,7 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
691
640
  }
692
641
 
693
642
  public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
694
- NSLog("[FlirManager] Discovery finished: \(iface)")
643
+ NSLog("[FlirManager] Discovery finished: \(iface.rawValue)")
695
644
  }
696
645
 
697
646
  public func cameraLost(_ cameraIdentity: FLIRIdentity) {
@@ -707,11 +656,9 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
707
656
  }
708
657
  }
709
658
  }
710
- #endif
711
659
 
712
660
  // MARK: - Camera Delegate
713
661
 
714
- #if FLIR_ENABLED
715
662
  extension FlirManager: FLIRDataReceivedDelegate {
716
663
  public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
717
664
  NSLog("[FlirManager] Camera disconnected: \(error?.localizedDescription ?? "clean")")
@@ -728,11 +675,9 @@ extension FlirManager: FLIRDataReceivedDelegate {
728
675
  }
729
676
  }
730
677
  }
731
- #endif
732
678
 
733
679
  // MARK: - Stream Delegate
734
680
 
735
- #if FLIR_ENABLED
736
681
  extension FlirManager: FLIRStreamDelegate {
737
682
  public func onError(_ error: Error) {
738
683
  NSLog("[FlirManager] Stream error: \(error)")
@@ -740,13 +685,10 @@ extension FlirManager: FLIRStreamDelegate {
740
685
  }
741
686
 
742
687
  public func onImageReceived() {
743
- NSLog("[FLIR-TRACE 1️⃣] onImageReceived called on SDK thread")
744
-
745
688
  // Process frame on dedicated render queue (matches sample app pattern)
746
689
  // This prevents blocking the SDK callback thread and main thread
747
690
  // Guard to skip frame if already processing (prevents backpressure/latency)
748
691
  guard !_isProcessingFrame else {
749
- NSLog("[FLIR-TRACE ⏩] Skipping frame (already processing)")
750
692
  return
751
693
  }
752
694
 
@@ -754,12 +696,9 @@ extension FlirManager: FLIRStreamDelegate {
754
696
  renderQueue.async { [weak self] in
755
697
  defer { self?._isProcessingFrame = false }
756
698
  guard let self = self, self._isStreaming, let streamer = self.streamer else {
757
- NSLog("[FLIR-TRACE ❌] No self, streamer or not streaming in renderQueue")
758
699
  return
759
700
  }
760
701
 
761
- NSLog("[FLIR-TRACE 2️⃣] Processing on renderQueue")
762
-
763
702
  objc_sync_enter(self.stateLock)
764
703
  let currentStreamer = self.streamer
765
704
  let streaming = self._isStreaming
@@ -778,7 +717,26 @@ extension FlirManager: FLIRStreamDelegate {
778
717
 
779
718
  streamer.withThermalImage { thermalImage in
780
719
  // 1. Apply Palette
781
- 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 {
782
740
  thermalImage.palette = palette
783
741
  }
784
742
 
@@ -791,29 +749,23 @@ extension FlirManager: FLIRStreamDelegate {
791
749
  NSLog("[FlirManager] Failed to save radiometric snapshot: \(error)")
792
750
  }
793
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
+ }
794
764
  }
795
-
796
- NSLog("[FLIR-TRACE 3️⃣] Streamer updated successfully")
797
765
  } catch {
798
- NSLog("[FLIR-TRACE ❌] Streamer update failed: \(error)")
799
- return
800
- }
801
-
802
- guard let image = streamer.getImage() else {
803
- NSLog("[FLIR-TRACE ❌] streamer.getImage() returned nil")
766
+ NSLog("[FlirManager] Streamer update failed: \(error)")
804
767
  return
805
768
  }
806
-
807
- NSLog("[FLIR-TRACE 4️⃣] Got image from streamer: \(image.size.width)x\(image.size.height)")
808
-
809
- self._latestImage = image
810
- let width = Int(image.size.width)
811
- let height = Int(image.size.height)
812
-
813
- DispatchQueue.main.async { [weak self] in
814
- NSLog("[FLIR-TRACE 5️⃣] Dispatching to delegate.onFrameReceived on main thread")
815
- self?.delegate?.onFrameReceived(image, width: width, height: height)
816
- }
817
769
  }
818
770
  }
819
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.7",
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",