cubevis 0.5.23__py3-none-any.whl → 0.5.24__py3-none-any.whl

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.
cubevis/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.5.23'
1
+ __version__ = '0.5.24'
cubevis/toolbox/_cube.py CHANGED
@@ -2969,8 +2969,8 @@ class CubeMask:
2969
2969
  ( spec ) => {
2970
2970
  if ( isource.masking_on( ) )
2971
2971
  refresh_pixel_display( spec.index,
2972
+ spec.spectrum.pixel[spec.chan[1]],
2972
2973
  'mask' in spec ? spec.mask[spec.chan[1]] : undefined,
2973
- 'mask' in spec && spec.mask[spec.chan[1]],
2974
2974
  pix_wrld && pix_wrld.label == 'pixel' ? false : true )
2975
2975
  } )
2976
2976
  if ( go_to && ! go_to._has_focus ) {
@@ -45,12 +45,13 @@ import asyncio
45
45
  import shutil
46
46
  import websockets
47
47
  from os.path import basename, abspath, exists, join
48
+ import numpy as np
48
49
  from uuid import uuid4
49
50
  from html import escape as html_escape
50
51
  from contextlib import asynccontextmanager
51
52
  from bokeh.models import Button, TextInput, Checkbox, Div, LinearAxis, CustomJS, Spacer, Span, HoverTool, DataRange1d, Step, InlineStyleSheet
52
53
  from bokeh.events import ModelEvent, MouseEnter
53
- from bokeh.models import TabPanel, Tabs
54
+ from bokeh.models import TabPanel, Tabs, Range1d
54
55
  from bokeh.plotting import ColumnDataSource, figure, show
55
56
  from bokeh.layouts import column, row, layout
56
57
  from bokeh.io import reset_output as reset_bokeh_output, output_notebook
@@ -343,57 +344,159 @@ class InteractiveCleanUI:
343
344
  </div>'''
344
345
 
345
346
  hover = HoverTool( tooltips=TOOLTIPS )
346
- imdetails['gui']['convergence'] = figure( sizing_mode=sizing_mode, y_axis_location="right",
347
- tools=[ hover ], toolbar_location=None, **kw )
348
347
 
349
- if orient == 'vertical':
350
- imdetails['gui']['convergence'].yaxis.axis_label='Iteration (cycle threshold dotted red)'
351
- imdetails['gui']['convergence'].xaxis.axis_label='Peak Residual'
352
- imdetails['gui']['convergence'].extra_x_ranges = { 'residual_range': DataRange1d( follow='end' ),
353
- 'flux_range': DataRange1d( follow='end' ) }
354
-
355
- imdetails['gui']['convergence'].step( 'values', 'iterations', source=imdetails['converge-data']['cyclethreshold'],
356
- line_color='red', x_range_name='residual_range', line_dash='dotted', line_width=2 )
357
- imdetails['gui']['convergence'].line( 'values', 'iterations', source=imdetails['converge-data']['residual'],
358
- line_color=self._converge_color['residual'], x_range_name='residual_range' )
359
- imdetails['gui']['convergence'].scatter( 'values', 'iterations', source=imdetails['converge-data']['residual'],
360
- color=self._converge_color['residual'], x_range_name='residual_range',size=10 )
361
- imdetails['gui']['convergence'].line( 'values', 'iterations', source=imdetails['converge-data']['flux'],
362
- line_color=self._converge_color['flux'], x_range_name='flux_range' )
363
- imdetails['gui']['convergence'].scatter( 'values', 'iterations', source=imdetails['converge-data']['flux'],
364
- color=self._converge_color['flux'], x_range_name='flux_range', size=10 )
365
-
366
- imdetails['gui']['convergence'].add_layout( LinearAxis( x_range_name='flux_range', axis_label='Total Flux',
367
- axis_line_color=self._converge_color['flux'],
368
- major_label_text_color=self._converge_color['flux'],
369
- axis_label_text_color=self._converge_color['flux'],
370
- major_tick_line_color=self._converge_color['flux'],
371
- minor_tick_line_color=self._converge_color['flux'] ), 'above')
348
+ ###
349
+ ### Data source that will be used for updating the convergence plot
350
+ ###
351
+ stokes = 0
352
+ convergence = imdetails['converge']['chan'][0][stokes]
353
+ imdetails['converge-data'] = { }
354
+ imdetails['converge-data']['flux'] = ColumnDataSource( data=dict( values=convergence['modelFlux'], iterations=convergence['iterations'],
355
+ cyclethreshold=convergence['cycleThresh'],
356
+ stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
357
+ type=['flux'] * len(convergence['iterations']) ) )
358
+ imdetails['converge-data']['residual'] = ColumnDataSource( data=dict( values=convergence['peakRes'], iterations=convergence['iterations'],
359
+ cyclethreshold=convergence['cycleThresh'],
360
+ stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
361
+ type=['residual'] * len(convergence['iterations'])) )
362
+ imdetails['converge-data']['cyclethreshold'] = ColumnDataSource( data=dict( values=convergence['cycleThresh'], iterations=convergence['iterations'] ) )
363
+
364
+ # Calculate explicit ranges for each dataset
365
+ flux_values = convergence['modelFlux']
366
+ residual_values = convergence['peakRes']
367
+ iterations = convergence['iterations']
368
+ cyclethresh_values = convergence['cycleThresh']
369
+
370
+ # Calculate ranges with padding
371
+ if len(flux_values) > 0:
372
+ flux_min, flux_max = np.min(flux_values), np.max(flux_values)
373
+ flux_padding = max((flux_max - flux_min) * 0.1, abs(flux_max * 0.05)) if flux_max != flux_min else abs(flux_max * 0.1)
374
+ else:
375
+ flux_min, flux_max, flux_padding = 0, 1, 0.1
372
376
 
377
+ if len(residual_values) > 0:
378
+ residual_min, residual_max = np.min(residual_values), np.max(residual_values)
379
+ residual_padding = max((residual_max - residual_min) * 0.1, abs(residual_max * 0.05)) if residual_max != residual_min else abs(residual_max * 0.1)
373
380
  else:
374
- imdetails['gui']['convergence'].xaxis.axis_label='Iteration (cycle threshold dotted red)'
375
- imdetails['gui']['convergence'].yaxis.axis_label='Peak Residual'
376
- imdetails['gui']['convergence'].extra_y_ranges = { 'residual_range': DataRange1d( follow='end' ),
377
- 'flux_range': DataRange1d( follow='end' ) }
378
-
379
- imdetails['gui']['convergence'].step( 'iterations', 'values', source=imdetails['converge-data']['cyclethreshold'],
380
- line_color='red', y_range_name='residual_range', line_dash='dotted', line_width=2 )
381
- imdetails['gui']['convergence'].line( 'iterations', 'values', source=imdetails['converge-data']['residual'],
382
- line_color=self._converge_color['residual'], y_range_name='residual_range' )
383
- imdetails['gui']['convergence'].scatter( 'iterations', 'values', source=imdetails['converge-data']['residual'],
384
- color=self._converge_color['residual'], y_range_name='residual_range',size=10 )
385
- imdetails['gui']['convergence'].line( 'iterations', 'values', source=imdetails['converge-data']['flux'],
386
- line_color=self._converge_color['flux'], y_range_name='flux_range' )
387
- imdetails['gui']['convergence'].scatter( 'iterations', 'values', source=imdetails['converge-data']['flux'],
388
- color=self._converge_color['flux'], y_range_name='flux_range', size=10 )
389
-
390
- imdetails['gui']['convergence'].add_layout( LinearAxis( y_range_name='flux_range', axis_label='Total Flux',
391
- axis_line_color=self._converge_color['flux'],
392
- major_label_text_color=self._converge_color['flux'],
393
- axis_label_text_color=self._converge_color['flux'],
394
- major_tick_line_color=self._converge_color['flux'],
395
- minor_tick_line_color=self._converge_color['flux'] ), 'right')
381
+ residual_min, residual_max, residual_padding = 0, 1, 0.1
382
+
383
+ # Create Range1d objects
384
+ flux_range = Range1d(start=flux_min - flux_padding, end=flux_max + flux_padding)
385
+ residual_range = Range1d(start=residual_min - residual_padding, end=residual_max + residual_padding)
386
+
387
+ # Ensure ranges are valid (non-zero span)
388
+ if flux_range.end - flux_range.start < 1e-10:
389
+ center = flux_range.start
390
+ if abs(center) < 1e-10: # If center is essentially 0
391
+ flux_range.start = -0.1
392
+ flux_range.end = 1.0
393
+ else:
394
+ span = max(abs(center * 0.2), 0.1)
395
+ flux_range.start = center - span
396
+ flux_range.end = center + span
397
+
398
+ if residual_range.end - residual_range.start < 1e-10:
399
+ center = residual_range.start
400
+ span = max(abs(center * 0.2), 0.1)
401
+ residual_range.start = center - span
402
+ residual_range.end = center + span
403
+
404
+ # ORIENTATION CONFIGURATION - this eliminates the conditional branches
405
+ config = {
406
+ 'vertical': {
407
+ 'iteration_axis': 'y',
408
+ 'data_axis': 'x',
409
+ 'main_axis_label': 'Iteration (cycle threshold dotted red)',
410
+ 'residual_axis_label': 'Peak Residual',
411
+ 'flux_axis_label': 'Total Flux',
412
+ 'residual_axis_pos': 'above',
413
+ 'flux_axis_pos': 'above',
414
+ 'extra_ranges_key': 'extra_x_ranges',
415
+ 'glyph_coords': ('values', 'iterations'), # (x, y)
416
+ 'range_name_param': 'x_range_name'
417
+ },
418
+ 'horizontal': {
419
+ 'iteration_axis': 'x',
420
+ 'data_axis': 'y',
421
+ 'main_axis_label': 'Iteration (cycle threshold dotted red)',
422
+ 'residual_axis_label': 'Peak Residual',
423
+ 'flux_axis_label': 'Total Flux',
424
+ 'residual_axis_pos': 'right',
425
+ 'flux_axis_pos': 'right',
426
+ 'extra_ranges_key': 'extra_y_ranges',
427
+ 'glyph_coords': ('iterations', 'values'), # (x, y)
428
+ 'range_name_param': 'y_range_name'
429
+ }
430
+ }
431
+
432
+ cfg = config[orient]
433
+
434
+ # Create figure with no default axes; to control the axes they must
435
+ # be explicitly set to None
436
+ imdetails['gui']['convergence'] = figure( sizing_mode=sizing_mode,
437
+ x_axis_location=None, y_axis_location=None,
438
+ tools=[ hover ], toolbar_location=None, **kw )
439
+
440
+ # Set up extra ranges
441
+ setattr(imdetails['gui']['convergence'], cfg['extra_ranges_key'], {
442
+ 'residual_range': residual_range,
443
+ 'flux_range': flux_range
444
+ })
445
+
446
+ # Store references for JavaScript updates
447
+ imdetails['converge-ranges'] = {
448
+ 'residual_range': residual_range,
449
+ 'flux_range': flux_range
450
+ }
396
451
 
452
+ # Create main iteration axis
453
+ main_axis = LinearAxis(axis_label=cfg['main_axis_label'])
454
+ main_axis_pos = 'below' if cfg['iteration_axis'] == 'x' else 'left'
455
+ imdetails['gui']['convergence'].add_layout(main_axis, main_axis_pos)
456
+
457
+ # Create glyphs using configuration
458
+ x_coord, y_coord = cfg['glyph_coords']
459
+ range_param = {cfg['range_name_param']: 'residual_range'}
460
+
461
+ imdetails['gui']['convergence'].step( x_coord, y_coord, source=imdetails['converge-data']['cyclethreshold'],
462
+ line_color='red', line_dash='dotted', line_width=2, **range_param )
463
+ imdetails['gui']['convergence'].line( x_coord, y_coord, source=imdetails['converge-data']['residual'],
464
+ line_color=self._converge_color['residual'], **range_param )
465
+ imdetails['gui']['convergence'].scatter( x_coord, y_coord, source=imdetails['converge-data']['residual'],
466
+ color=self._converge_color['residual'], size=10, **range_param )
467
+
468
+ # Flux glyphs
469
+ range_param = {cfg['range_name_param']: 'flux_range'}
470
+ imdetails['gui']['convergence'].line( x_coord, y_coord, source=imdetails['converge-data']['flux'],
471
+ line_color=self._converge_color['flux'], **range_param )
472
+ imdetails['gui']['convergence'].scatter( x_coord, y_coord, source=imdetails['converge-data']['flux'],
473
+ color=self._converge_color['flux'], size=10, **range_param )
474
+
475
+ # Create and style residual axis
476
+ residual_axis_param = {cfg['range_name_param'].replace('_name', '_name'): 'residual_range'}
477
+ residual_axis = LinearAxis(axis_label=cfg['residual_axis_label'], **residual_axis_param)
478
+ residual_axis.axis_line_color = self._converge_color['residual']
479
+ residual_axis.major_label_text_color = self._converge_color['residual']
480
+ residual_axis.axis_label_text_color = self._converge_color['residual']
481
+ residual_axis.major_tick_line_color = self._converge_color['residual']
482
+ residual_axis.minor_tick_line_color = self._converge_color['residual']
483
+ imdetails['gui']['convergence'].add_layout(residual_axis, cfg['residual_axis_pos'])
484
+
485
+ # Create and style flux axis
486
+ flux_axis_param = {cfg['range_name_param'].replace('_name', '_name'): 'flux_range'}
487
+ flux_axis = LinearAxis(axis_label=cfg['flux_axis_label'], **flux_axis_param)
488
+ flux_axis.axis_line_color = self._converge_color['flux']
489
+ flux_axis.major_label_text_color = self._converge_color['flux']
490
+ flux_axis.axis_label_text_color = self._converge_color['flux']
491
+ flux_axis.major_tick_line_color = self._converge_color['flux']
492
+ flux_axis.minor_tick_line_color = self._converge_color['flux']
493
+ imdetails['gui']['convergence'].add_layout(flux_axis, cfg['flux_axis_pos'])
494
+
495
+ # Store axis references for JavaScript access
496
+ imdetails['converge-axes'] = {
497
+ 'residual_axis': residual_axis,
498
+ 'flux_axis': flux_axis
499
+ }
397
500
 
398
501
  def _launch_gui( self ):
399
502
  '''create and show GUI
@@ -534,23 +637,6 @@ class InteractiveCleanUI:
534
637
 
535
638
  self._clean['converge']['pipe'].register( self._clean['converge']['id'], convergence_handler )
536
639
 
537
- ###
538
- ### Data source that will be used for updating the convergence plot
539
- ###
540
- stokes = 0
541
- convergence = imdetails['converge']['chan'][0][stokes]
542
- imdetails['converge-data'] = { }
543
- imdetails['converge-data']['flux'] = ColumnDataSource( data=dict( values=convergence['modelFlux'], iterations=convergence['iterations'],
544
- cyclethreshold=convergence['cycleThresh'],
545
- stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
546
- type=['flux'] * len(convergence['iterations']) ) )
547
- imdetails['converge-data']['residual'] = ColumnDataSource( data=dict( values=convergence['peakRes'], iterations=convergence['iterations'],
548
- cyclethreshold=convergence['cycleThresh'],
549
- stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
550
- type=['residual'] * len(convergence['iterations'])) )
551
- imdetails['converge-data']['cyclethreshold'] = ColumnDataSource( data=dict( values=convergence['cycleThresh'], iterations=convergence['iterations'] ) )
552
-
553
-
554
640
  ###
555
641
  ### help page for cube interactions
556
642
  ###
@@ -585,8 +671,12 @@ class InteractiveCleanUI:
585
671
  imdetails['gui']['image']['src'] = imdetails['gui']['cube'].js_obj( )
586
672
  imdetails['gui']['image']['fig'] = imdetails['gui']['cube'].image( grid=False, height_policy='max', width_policy='max',
587
673
  channelcb=CustomJS( args=dict( img_state={ 'src': imdetails['gui']['image']['src'],
588
- 'flux': imdetails['converge-data']['flux'],
589
- 'residual': imdetails['converge-data']['residual'],
674
+ 'flux': { 'source': imdetails['converge-data']['flux'],
675
+ 'axis': imdetails['converge-axes']['flux_axis'],
676
+ 'range': imdetails['converge-ranges']['flux_range'] },
677
+ 'residual': { 'source': imdetails['converge-data']['residual'],
678
+ 'axis': imdetails['converge-axes']['residual_axis'],
679
+ 'range': imdetails['converge-ranges']['residual_range'] },
590
680
  'cyclethreshold': imdetails['converge-data']['cyclethreshold'] },
591
681
  imid=imid,
592
682
  ctrl={ 'converge': self._clean['converge'] },
@@ -702,9 +792,13 @@ class InteractiveCleanUI:
702
792
  'src': v['gui']['cube'].js_obj( ),
703
793
  'spectrum': v['gui']['spectrum'],
704
794
  'src': v['gui']['image']['src'],
705
- 'flux': v['converge-data']['flux'],
795
+ 'flux': { 'source': v['converge-data']['flux'],
796
+ 'axis': v['converge-axes']['flux_axis'],
797
+ 'range': v['converge-ranges']['flux_range'] },
706
798
  'cyclethreshold': v['converge-data']['cyclethreshold'],
707
- 'residual': v['converge-data']['residual'],
799
+ 'residual': { 'source': v['converge-data']['residual'],
800
+ 'axis': v['converge-axes']['residual_axis'],
801
+ 'range': v['converge-ranges']['residual_range'] },
708
802
  'navi': { 'slider': v['gui']['slider'],
709
803
  'goto': v['gui']['goto'],
710
804
  ## it doesn't seem like pixel tracking must be disabled
@@ -1216,7 +1310,52 @@ class InteractiveCleanUI:
1216
1310
  ### -- The "Insert here ..." code seems to be called when when the stokes plane is changed --
1217
1311
  ### -- but there have been no tclean iterations yet... --
1218
1312
  ### --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
1219
- 'update-converge': '''function update_convergence_single( target, data ) {
1313
+ 'update-converge': '''function updateAxisRange(source, range_obj, axis_obj, axis_name) {
1314
+ const data = source.data;
1315
+ const values = data['values'];
1316
+
1317
+ if (values.length === 0) return;
1318
+
1319
+ // Calculate data bounds
1320
+ const min_val = Math.min(...values);
1321
+ const max_val = Math.max(...values);
1322
+
1323
+ // Add padding (10% of range, with minimum padding)
1324
+ const span = max_val - min_val;
1325
+ const padding = max_val != min_val ? Math.max(span * 0.1, Math.abs(max_val * 0.02)) : Math.abs(max_val * 0.1);
1326
+
1327
+ // Update range
1328
+ range_obj.start = min_val - padding;
1329
+ range_obj.end = max_val + padding;
1330
+
1331
+ // Ensure ranges are valid (non-zero span)
1332
+ if ( range_obj.end - range_obj.start < 1e-10 ) {
1333
+ const center = range_obj.start
1334
+ if ( Math.abs(center) < 1e-10 ) {
1335
+ // If center is essentially 0
1336
+ range_obj.start = -0.1
1337
+ range_obj.end = 1.0
1338
+ } else {
1339
+ const span = Math.max(Math.abs(center * 0.2), 0.1)
1340
+ range_obj.start = center - span
1341
+ range_obj.end = center + span
1342
+ }
1343
+ }
1344
+
1345
+ // Optional: Update tick density
1346
+ if (axis_obj && axis_obj.ticker) {
1347
+ const new_span = range_obj.end - range_obj.start;
1348
+ if (new_span < 10) {
1349
+ axis_obj.ticker.desired_num_ticks = 8;
1350
+ } else if (new_span < 100) {
1351
+ axis_obj.ticker.desired_num_ticks = 10;
1352
+ } else {
1353
+ axis_obj.ticker.desired_num_ticks = 6;
1354
+ }
1355
+ }
1356
+ }
1357
+
1358
+ function update_convergence_single( target, data ) {
1220
1359
  const pos = target.src.cur_chan
1221
1360
  const imdata = data.get(pos[1]).get(pos[0])
1222
1361
  // chan----------------^^^^^^ ^^^^^^----stokes
@@ -1226,9 +1365,11 @@ class InteractiveCleanUI:
1226
1365
  const modelFlux = imdata.modelFlux
1227
1366
  const stopCode = imdata.stopCode
1228
1367
  const stopDesc = imdata.stopCode.map( code => stopdescmap.has(code) ? stopdescmap.get(code): "" )
1229
- target.residual.data = { iterations, cyclethreshold, stopDesc, values: peakRes, type: Array(iterations.length).fill('residual') }
1230
- target.flux.data = { iterations, cyclethreshold, stopDesc, values: modelFlux, type: Array(iterations.length).fill('flux') }
1368
+ target.residual.source.data = { iterations, cyclethreshold, stopDesc, values: peakRes, type: Array(iterations.length).fill('residual') }
1369
+ target.flux.source.data = { iterations, cyclethreshold, stopDesc, values: modelFlux, type: Array(iterations.length).fill('flux') }
1231
1370
  target.cyclethreshold.data = { iterations, values: cyclethreshold }
1371
+ updateAxisRange( target.flux.source, target.flux.range, target.flux.axis, 'Flux' )
1372
+ updateAxisRange( target.residual.source, target.residual.range, target.residual.axis, 'Residual' )
1232
1373
  }
1233
1374
 
1234
1375
  function update_convergence( recurse=false ) {
@@ -44,12 +44,13 @@ import asyncio
44
44
  import shutil
45
45
  import websockets
46
46
  from os.path import basename, abspath, exists, join
47
+ import numpy as np
47
48
  from uuid import uuid4
48
49
  from html import escape as html_escape
49
50
  from contextlib import asynccontextmanager
50
51
  from bokeh.models import Button, TextInput, Checkbox, Div, LinearAxis, CustomJS, Spacer, Span, HoverTool, DataRange1d, Step, InlineStyleSheet
51
52
  from bokeh.events import ModelEvent, MouseEnter
52
- from bokeh.models import TabPanel, Tabs
53
+ from bokeh.models import TabPanel, Tabs, Range1d
53
54
  from bokeh.plotting import ColumnDataSource, figure, show
54
55
  from bokeh.layouts import column, row, layout
55
56
  from bokeh.io import reset_output as reset_bokeh_output, output_notebook
@@ -342,57 +343,159 @@ class InteractiveCleanUI:
342
343
  </div>'''
343
344
 
344
345
  hover = HoverTool( tooltips=TOOLTIPS )
345
- imdetails['gui']['convergence'] = figure( sizing_mode=sizing_mode, y_axis_location="right",
346
- tools=[ hover ], toolbar_location=None, **kw )
347
346
 
348
- if orient == 'vertical':
349
- imdetails['gui']['convergence'].yaxis.axis_label='Iteration (cycle threshold dotted red)'
350
- imdetails['gui']['convergence'].xaxis.axis_label='Peak Residual'
351
- imdetails['gui']['convergence'].extra_x_ranges = { 'residual_range': DataRange1d( follow='end' ),
352
- 'flux_range': DataRange1d( follow='end' ) }
353
-
354
- imdetails['gui']['convergence'].step( 'values', 'iterations', source=imdetails['converge-data']['cyclethreshold'],
355
- line_color='red', x_range_name='residual_range', line_dash='dotted', line_width=2 )
356
- imdetails['gui']['convergence'].line( 'values', 'iterations', source=imdetails['converge-data']['residual'],
357
- line_color=self._converge_color['residual'], x_range_name='residual_range' )
358
- imdetails['gui']['convergence'].scatter( 'values', 'iterations', source=imdetails['converge-data']['residual'],
359
- color=self._converge_color['residual'], x_range_name='residual_range',size=10 )
360
- imdetails['gui']['convergence'].line( 'values', 'iterations', source=imdetails['converge-data']['flux'],
361
- line_color=self._converge_color['flux'], x_range_name='flux_range' )
362
- imdetails['gui']['convergence'].scatter( 'values', 'iterations', source=imdetails['converge-data']['flux'],
363
- color=self._converge_color['flux'], x_range_name='flux_range', size=10 )
364
-
365
- imdetails['gui']['convergence'].add_layout( LinearAxis( x_range_name='flux_range', axis_label='Total Flux',
366
- axis_line_color=self._converge_color['flux'],
367
- major_label_text_color=self._converge_color['flux'],
368
- axis_label_text_color=self._converge_color['flux'],
369
- major_tick_line_color=self._converge_color['flux'],
370
- minor_tick_line_color=self._converge_color['flux'] ), 'above')
347
+ ###
348
+ ### Data source that will be used for updating the convergence plot
349
+ ###
350
+ stokes = 0
351
+ convergence = imdetails['converge']['chan'][0][stokes]
352
+ imdetails['converge-data'] = { }
353
+ imdetails['converge-data']['flux'] = ColumnDataSource( data=dict( values=convergence['modelFlux'], iterations=convergence['iterations'],
354
+ cyclethreshold=convergence['cycleThresh'],
355
+ stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
356
+ type=['flux'] * len(convergence['iterations']) ) )
357
+ imdetails['converge-data']['residual'] = ColumnDataSource( data=dict( values=convergence['peakRes'], iterations=convergence['iterations'],
358
+ cyclethreshold=convergence['cycleThresh'],
359
+ stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
360
+ type=['residual'] * len(convergence['iterations'])) )
361
+ imdetails['converge-data']['cyclethreshold'] = ColumnDataSource( data=dict( values=convergence['cycleThresh'], iterations=convergence['iterations'] ) )
362
+
363
+ # Calculate explicit ranges for each dataset
364
+ flux_values = convergence['modelFlux']
365
+ residual_values = convergence['peakRes']
366
+ iterations = convergence['iterations']
367
+ cyclethresh_values = convergence['cycleThresh']
368
+
369
+ # Calculate ranges with padding
370
+ if len(flux_values) > 0:
371
+ flux_min, flux_max = np.min(flux_values), np.max(flux_values)
372
+ flux_padding = max((flux_max - flux_min) * 0.1, abs(flux_max * 0.05)) if flux_max != flux_min else abs(flux_max * 0.1)
373
+ else:
374
+ flux_min, flux_max, flux_padding = 0, 1, 0.1
371
375
 
376
+ if len(residual_values) > 0:
377
+ residual_min, residual_max = np.min(residual_values), np.max(residual_values)
378
+ residual_padding = max((residual_max - residual_min) * 0.1, abs(residual_max * 0.05)) if residual_max != residual_min else abs(residual_max * 0.1)
372
379
  else:
373
- imdetails['gui']['convergence'].xaxis.axis_label='Iteration (cycle threshold dotted red)'
374
- imdetails['gui']['convergence'].yaxis.axis_label='Peak Residual'
375
- imdetails['gui']['convergence'].extra_y_ranges = { 'residual_range': DataRange1d( follow='end' ),
376
- 'flux_range': DataRange1d( follow='end' ) }
377
-
378
- imdetails['gui']['convergence'].step( 'iterations', 'values', source=imdetails['converge-data']['cyclethreshold'],
379
- line_color='red', y_range_name='residual_range', line_dash='dotted', line_width=2 )
380
- imdetails['gui']['convergence'].line( 'iterations', 'values', source=imdetails['converge-data']['residual'],
381
- line_color=self._converge_color['residual'], y_range_name='residual_range' )
382
- imdetails['gui']['convergence'].scatter( 'iterations', 'values', source=imdetails['converge-data']['residual'],
383
- color=self._converge_color['residual'], y_range_name='residual_range',size=10 )
384
- imdetails['gui']['convergence'].line( 'iterations', 'values', source=imdetails['converge-data']['flux'],
385
- line_color=self._converge_color['flux'], y_range_name='flux_range' )
386
- imdetails['gui']['convergence'].scatter( 'iterations', 'values', source=imdetails['converge-data']['flux'],
387
- color=self._converge_color['flux'], y_range_name='flux_range', size=10 )
388
-
389
- imdetails['gui']['convergence'].add_layout( LinearAxis( y_range_name='flux_range', axis_label='Total Flux',
390
- axis_line_color=self._converge_color['flux'],
391
- major_label_text_color=self._converge_color['flux'],
392
- axis_label_text_color=self._converge_color['flux'],
393
- major_tick_line_color=self._converge_color['flux'],
394
- minor_tick_line_color=self._converge_color['flux'] ), 'right')
380
+ residual_min, residual_max, residual_padding = 0, 1, 0.1
381
+
382
+ # Create Range1d objects
383
+ flux_range = Range1d(start=flux_min - flux_padding, end=flux_max + flux_padding)
384
+ residual_range = Range1d(start=residual_min - residual_padding, end=residual_max + residual_padding)
385
+
386
+ # Ensure ranges are valid (non-zero span)
387
+ if flux_range.end - flux_range.start < 1e-10:
388
+ center = flux_range.start
389
+ if abs(center) < 1e-10: # If center is essentially 0
390
+ flux_range.start = -0.1
391
+ flux_range.end = 1.0
392
+ else:
393
+ span = max(abs(center * 0.2), 0.1)
394
+ flux_range.start = center - span
395
+ flux_range.end = center + span
396
+
397
+ if residual_range.end - residual_range.start < 1e-10:
398
+ center = residual_range.start
399
+ span = max(abs(center * 0.2), 0.1)
400
+ residual_range.start = center - span
401
+ residual_range.end = center + span
402
+
403
+ # ORIENTATION CONFIGURATION - this eliminates the conditional branches
404
+ config = {
405
+ 'vertical': {
406
+ 'iteration_axis': 'y',
407
+ 'data_axis': 'x',
408
+ 'main_axis_label': 'Iteration (cycle threshold dotted red)',
409
+ 'residual_axis_label': 'Peak Residual',
410
+ 'flux_axis_label': 'Total Flux',
411
+ 'residual_axis_pos': 'above',
412
+ 'flux_axis_pos': 'above',
413
+ 'extra_ranges_key': 'extra_x_ranges',
414
+ 'glyph_coords': ('values', 'iterations'), # (x, y)
415
+ 'range_name_param': 'x_range_name'
416
+ },
417
+ 'horizontal': {
418
+ 'iteration_axis': 'x',
419
+ 'data_axis': 'y',
420
+ 'main_axis_label': 'Iteration (cycle threshold dotted red)',
421
+ 'residual_axis_label': 'Peak Residual',
422
+ 'flux_axis_label': 'Total Flux',
423
+ 'residual_axis_pos': 'right',
424
+ 'flux_axis_pos': 'right',
425
+ 'extra_ranges_key': 'extra_y_ranges',
426
+ 'glyph_coords': ('iterations', 'values'), # (x, y)
427
+ 'range_name_param': 'y_range_name'
428
+ }
429
+ }
430
+
431
+ cfg = config[orient]
432
+
433
+ # Create figure with no default axes; to control the axes they must
434
+ # be explicitly set to None
435
+ imdetails['gui']['convergence'] = figure( sizing_mode=sizing_mode,
436
+ x_axis_location=None, y_axis_location=None,
437
+ tools=[ hover ], toolbar_location=None, **kw )
438
+
439
+ # Set up extra ranges
440
+ setattr(imdetails['gui']['convergence'], cfg['extra_ranges_key'], {
441
+ 'residual_range': residual_range,
442
+ 'flux_range': flux_range
443
+ })
444
+
445
+ # Store references for JavaScript updates
446
+ imdetails['converge-ranges'] = {
447
+ 'residual_range': residual_range,
448
+ 'flux_range': flux_range
449
+ }
395
450
 
451
+ # Create main iteration axis
452
+ main_axis = LinearAxis(axis_label=cfg['main_axis_label'])
453
+ main_axis_pos = 'below' if cfg['iteration_axis'] == 'x' else 'left'
454
+ imdetails['gui']['convergence'].add_layout(main_axis, main_axis_pos)
455
+
456
+ # Create glyphs using configuration
457
+ x_coord, y_coord = cfg['glyph_coords']
458
+ range_param = {cfg['range_name_param']: 'residual_range'}
459
+
460
+ imdetails['gui']['convergence'].step( x_coord, y_coord, source=imdetails['converge-data']['cyclethreshold'],
461
+ line_color='red', line_dash='dotted', line_width=2, **range_param )
462
+ imdetails['gui']['convergence'].line( x_coord, y_coord, source=imdetails['converge-data']['residual'],
463
+ line_color=self._converge_color['residual'], **range_param )
464
+ imdetails['gui']['convergence'].scatter( x_coord, y_coord, source=imdetails['converge-data']['residual'],
465
+ color=self._converge_color['residual'], size=10, **range_param )
466
+
467
+ # Flux glyphs
468
+ range_param = {cfg['range_name_param']: 'flux_range'}
469
+ imdetails['gui']['convergence'].line( x_coord, y_coord, source=imdetails['converge-data']['flux'],
470
+ line_color=self._converge_color['flux'], **range_param )
471
+ imdetails['gui']['convergence'].scatter( x_coord, y_coord, source=imdetails['converge-data']['flux'],
472
+ color=self._converge_color['flux'], size=10, **range_param )
473
+
474
+ # Create and style residual axis
475
+ residual_axis_param = {cfg['range_name_param'].replace('_name', '_name'): 'residual_range'}
476
+ residual_axis = LinearAxis(axis_label=cfg['residual_axis_label'], **residual_axis_param)
477
+ residual_axis.axis_line_color = self._converge_color['residual']
478
+ residual_axis.major_label_text_color = self._converge_color['residual']
479
+ residual_axis.axis_label_text_color = self._converge_color['residual']
480
+ residual_axis.major_tick_line_color = self._converge_color['residual']
481
+ residual_axis.minor_tick_line_color = self._converge_color['residual']
482
+ imdetails['gui']['convergence'].add_layout(residual_axis, cfg['residual_axis_pos'])
483
+
484
+ # Create and style flux axis
485
+ flux_axis_param = {cfg['range_name_param'].replace('_name', '_name'): 'flux_range'}
486
+ flux_axis = LinearAxis(axis_label=cfg['flux_axis_label'], **flux_axis_param)
487
+ flux_axis.axis_line_color = self._converge_color['flux']
488
+ flux_axis.major_label_text_color = self._converge_color['flux']
489
+ flux_axis.axis_label_text_color = self._converge_color['flux']
490
+ flux_axis.major_tick_line_color = self._converge_color['flux']
491
+ flux_axis.minor_tick_line_color = self._converge_color['flux']
492
+ imdetails['gui']['convergence'].add_layout(flux_axis, cfg['flux_axis_pos'])
493
+
494
+ # Store axis references for JavaScript access
495
+ imdetails['converge-axes'] = {
496
+ 'residual_axis': residual_axis,
497
+ 'flux_axis': flux_axis
498
+ }
396
499
 
397
500
  def _launch_gui( self ):
398
501
  '''create and show GUI
@@ -533,23 +636,6 @@ class InteractiveCleanUI:
533
636
 
534
637
  self._clean['converge']['pipe'].register( self._clean['converge']['id'], convergence_handler )
535
638
 
536
- ###
537
- ### Data source that will be used for updating the convergence plot
538
- ###
539
- stokes = 0
540
- convergence = imdetails['converge']['chan'][0][stokes]
541
- imdetails['converge-data'] = { }
542
- imdetails['converge-data']['flux'] = ColumnDataSource( data=dict( values=convergence['modelFlux'], iterations=convergence['iterations'],
543
- cyclethreshold=convergence['cycleThresh'],
544
- stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
545
- type=['flux'] * len(convergence['iterations']) ) )
546
- imdetails['converge-data']['residual'] = ColumnDataSource( data=dict( values=convergence['peakRes'], iterations=convergence['iterations'],
547
- cyclethreshold=convergence['cycleThresh'],
548
- stopDesc=list( map( ImagingDict.get_summaryminor_stopdesc, convergence['stopCode'] ) ),
549
- type=['residual'] * len(convergence['iterations'])) )
550
- imdetails['converge-data']['cyclethreshold'] = ColumnDataSource( data=dict( values=convergence['cycleThresh'], iterations=convergence['iterations'] ) )
551
-
552
-
553
639
  ###
554
640
  ### help page for cube interactions
555
641
  ###
@@ -584,8 +670,12 @@ class InteractiveCleanUI:
584
670
  imdetails['gui']['image']['src'] = imdetails['gui']['cube'].js_obj( )
585
671
  imdetails['gui']['image']['fig'] = imdetails['gui']['cube'].image( grid=False, height_policy='max', width_policy='max',
586
672
  channelcb=CustomJS( args=dict( img_state={ 'src': imdetails['gui']['image']['src'],
587
- 'flux': imdetails['converge-data']['flux'],
588
- 'residual': imdetails['converge-data']['residual'],
673
+ 'flux': { 'source': imdetails['converge-data']['flux'],
674
+ 'axis': imdetails['converge-axes']['flux_axis'],
675
+ 'range': imdetails['converge-ranges']['flux_range'] },
676
+ 'residual': { 'source': imdetails['converge-data']['residual'],
677
+ 'axis': imdetails['converge-axes']['residual_axis'],
678
+ 'range': imdetails['converge-ranges']['residual_range'] },
589
679
  'cyclethreshold': imdetails['converge-data']['cyclethreshold'] },
590
680
  imid=imid,
591
681
  ctrl={ 'converge': self._clean['converge'] },
@@ -701,9 +791,13 @@ class InteractiveCleanUI:
701
791
  'src': v['gui']['cube'].js_obj( ),
702
792
  'spectrum': v['gui']['spectrum'],
703
793
  'src': v['gui']['image']['src'],
704
- 'flux': v['converge-data']['flux'],
794
+ 'flux': { 'source': v['converge-data']['flux'],
795
+ 'axis': v['converge-axes']['flux_axis'],
796
+ 'range': v['converge-ranges']['flux_range'] },
705
797
  'cyclethreshold': v['converge-data']['cyclethreshold'],
706
- 'residual': v['converge-data']['residual'],
798
+ 'residual': { 'source': v['converge-data']['residual'],
799
+ 'axis': v['converge-axes']['residual_axis'],
800
+ 'range': v['converge-ranges']['residual_range'] },
707
801
  'navi': { 'slider': v['gui']['slider'],
708
802
  'goto': v['gui']['goto'],
709
803
  ## it doesn't seem like pixel tracking must be disabled
@@ -1215,7 +1309,52 @@ class InteractiveCleanUI:
1215
1309
  ### -- The "Insert here ..." code seems to be called when when the stokes plane is changed --
1216
1310
  ### -- but there have been no tclean iterations yet... --
1217
1311
  ### --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
1218
- 'update-converge': '''function update_convergence_single( target, data ) {
1312
+ 'update-converge': '''function updateAxisRange(source, range_obj, axis_obj, axis_name) {
1313
+ const data = source.data;
1314
+ const values = data['values'];
1315
+
1316
+ if (values.length === 0) return;
1317
+
1318
+ // Calculate data bounds
1319
+ const min_val = Math.min(...values);
1320
+ const max_val = Math.max(...values);
1321
+
1322
+ // Add padding (10% of range, with minimum padding)
1323
+ const span = max_val - min_val;
1324
+ const padding = max_val != min_val ? Math.max(span * 0.1, Math.abs(max_val * 0.02)) : Math.abs(max_val * 0.1);
1325
+
1326
+ // Update range
1327
+ range_obj.start = min_val - padding;
1328
+ range_obj.end = max_val + padding;
1329
+
1330
+ // Ensure ranges are valid (non-zero span)
1331
+ if ( range_obj.end - range_obj.start < 1e-10 ) {
1332
+ const center = range_obj.start
1333
+ if ( Math.abs(center) < 1e-10 ) {
1334
+ // If center is essentially 0
1335
+ range_obj.start = -0.1
1336
+ range_obj.end = 1.0
1337
+ } else {
1338
+ const span = Math.max(Math.abs(center * 0.2), 0.1)
1339
+ range_obj.start = center - span
1340
+ range_obj.end = center + span
1341
+ }
1342
+ }
1343
+
1344
+ // Optional: Update tick density
1345
+ if (axis_obj && axis_obj.ticker) {
1346
+ const new_span = range_obj.end - range_obj.start;
1347
+ if (new_span < 10) {
1348
+ axis_obj.ticker.desired_num_ticks = 8;
1349
+ } else if (new_span < 100) {
1350
+ axis_obj.ticker.desired_num_ticks = 10;
1351
+ } else {
1352
+ axis_obj.ticker.desired_num_ticks = 6;
1353
+ }
1354
+ }
1355
+ }
1356
+
1357
+ function update_convergence_single( target, data ) {
1219
1358
  const pos = target.src.cur_chan
1220
1359
  const imdata = data.get(pos[1]).get(pos[0])
1221
1360
  // chan----------------^^^^^^ ^^^^^^----stokes
@@ -1225,9 +1364,11 @@ class InteractiveCleanUI:
1225
1364
  const modelFlux = imdata.modelFlux
1226
1365
  const stopCode = imdata.stopCode
1227
1366
  const stopDesc = imdata.stopCode.map( code => stopdescmap.has(code) ? stopdescmap.get(code): "" )
1228
- target.residual.data = { iterations, cyclethreshold, stopDesc, values: peakRes, type: Array(iterations.length).fill('residual') }
1229
- target.flux.data = { iterations, cyclethreshold, stopDesc, values: modelFlux, type: Array(iterations.length).fill('flux') }
1367
+ target.residual.source.data = { iterations, cyclethreshold, stopDesc, values: peakRes, type: Array(iterations.length).fill('residual') }
1368
+ target.flux.source.data = { iterations, cyclethreshold, stopDesc, values: modelFlux, type: Array(iterations.length).fill('flux') }
1230
1369
  target.cyclethreshold.data = { iterations, values: cyclethreshold }
1370
+ updateAxisRange( target.flux.source, target.flux.range, target.flux.axis, 'Flux' )
1371
+ updateAxisRange( target.residual.source, target.residual.range, target.residual.axis, 'Residual' )
1231
1372
  }
1232
1373
 
1233
1374
  function update_convergence( recurse=false ) {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cubevis
3
- Version: 0.5.23
3
+ Version: 0.5.24
4
4
  Summary: visualization toolkit and apps for casa
5
5
  License: LGPL
6
6
  Author-email: Darrell Schiebel <darrell@schiebel.us>,Pam Harris <pharris@nrao.edu>
@@ -95,9 +95,9 @@ cubevis/remote/_local.py,sha256=PcPCFcwttTFZd3O33-5pqDuGKQKK6CA0gz1MTIkTiNI,1032
95
95
  cubevis/remote/_remote_kernel.py,sha256=wfu7ZzKn-oCxZxzDIkC5puBvGf8WbCLYL3CzM56_FNc,2652
96
96
  cubevis/toolbox/__init__.py,sha256=xzqwAG9863d7UKBVBRw7FrRUQbvCdFcLBq4vTpg63DU,1487
97
97
  cubevis/toolbox/_app_context.py,sha256=0tRY2SSbSCM6RKLFs_T707_ehWkJXPvnLlE1P9cLXJY,3024
98
- cubevis/toolbox/_cube.py,sha256=T-9I4f_rpiZRarT6rOzCwgveQCnWp7sV1_jNf6OX0lU,294698
99
- cubevis/toolbox/_interactive_clean_ui.mustache,sha256=DKmy6dMpGOq4aZImorhF0K2wOV0oi58qkv7o4CdDJEM,103985
100
- cubevis/toolbox/_interactive_clean_ui.py,sha256=I9ikjAJjN64gxql_CXoiUCg5wTclrV1pENGbDVczFic,103896
98
+ cubevis/toolbox/_cube.py,sha256=Qj6aY_0x9nZkdIymGSwEFhBYg_tXv7H5zrjERAFLwXQ,294690
99
+ cubevis/toolbox/_interactive_clean_ui.mustache,sha256=KtcTGDa-vBB2_hIug7kNimICcJk1KavS0vMyf_qGRIE,111003
100
+ cubevis/toolbox/_interactive_clean_ui.py,sha256=iaBpVin0iRBvcW1mXpSjEj6rTyQn3f3Zhd-AHS1fqOw,110914
101
101
  cubevis/toolbox/_interactiveclean_wrappers.py,sha256=XqyCGz33CMDhszTxnwZ_3-64GszUK1XYnGKUOxl9sas,5071
102
102
  cubevis/toolbox/_region_list.py,sha256=_1RvnXwqMoaAq8CPy-6IyhabLi_snXqO566onehI2y0,8957
103
103
  cubevis/utils/_ResourceManager.py,sha256=SaaR29etabRiKxmUK-aWvAm4v_OPFJH8CX7bNFm0Lgo,3410
@@ -114,8 +114,8 @@ cubevis/utils/_pkgs.py,sha256=mu2CCzndmJZYP81UkFhxveW_CisWLUvagJVolHOEVgM,2294
114
114
  cubevis/utils/_regions.py,sha256=TdAg4ZUUyhg3nFmX9_KLboqmc0LkyOdEW8M1WDR5Udk,1669
115
115
  cubevis/utils/_static.py,sha256=rN-sqXNqQ5R2M3wmPHU1GPP5OTyyWQlUPRuimCrht-g,2347
116
116
  cubevis/utils/_tiles.py,sha256=A9W1X61VOhBMTOKXVajzOIoiV2FBdO5N2SFB9SUpDOo,7336
117
- cubevis/__version__.py,sha256=I49ytBecDTKF_6KFJBcXA0zcgUvvXhff2l52LZ7lz8M,22
118
- cubevis-0.5.23.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
119
- cubevis-0.5.23.dist-info/METADATA,sha256=X0m2sjmGhdkEO7wR87wpOudzfiMzU8EjyLTIoq9Rd7w,2629
120
- cubevis-0.5.23.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
121
- cubevis-0.5.23.dist-info/RECORD,,
117
+ cubevis/__version__.py,sha256=aydFbhyyvNka8P6vHcHfdWlGmQCUwCmq7WC1N73zpFA,22
118
+ cubevis-0.5.24.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
119
+ cubevis-0.5.24.dist-info/METADATA,sha256=ISXKMsHZaJ2gDD1q-D_Jedxy0cmAP7iyccFmnsjllEk,2629
120
+ cubevis-0.5.24.dist-info/licenses/LICENSE,sha256=IMF9i4xIpgCADf0U-V1cuf9HBmqWQd3qtI3FSuyW4zE,26526
121
+ cubevis-0.5.24.dist-info/RECORD,,