pymakeplots 0.2.0__tar.gz → 0.2.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymakeplots
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Home-page: https://github.com/TimothyADavis/pymakeplots
5
5
  Author: Timothy A. Davis
6
6
  Author-email: DavisT@cardiff.ac.uk
@@ -11,6 +11,13 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Operating System :: OS Independent
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE.md
14
+ Requires-Dist: numpy
15
+ Requires-Dist: matplotlib>3.3.1
16
+ Requires-Dist: scipy>=1.14.0
17
+ Requires-Dist: astropy
18
+ Requires-Dist: spectral-cube
19
+ Requires-Dist: radio-beam
20
+ Requires-Dist: pafit
14
21
 
15
22
  [![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-382/) [![PyPI version](https://badge.fury.io/py/pymakeplots.svg)](https://badge.fury.io/py/pymakeplots)
16
23
 
@@ -12,8 +12,8 @@ from mpl_toolkits.axes_grid1 import make_axes_locatable
12
12
  from matplotlib.patches import Ellipse,Rectangle
13
13
  from matplotlib import cm
14
14
  from matplotlib.colors import ListedColormap, LinearSegmentedColormap
15
- from matplotlib.offsetbox import AnchoredText
16
- from mpl_toolkits.axes_grid1.anchored_artists import AnchoredEllipse,AnchoredSizeBar
15
+ from matplotlib.offsetbox import AnchoredText,AuxTransformBox, AnchoredOffsetbox
16
+ from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBar
17
17
  from astropy.coordinates import ICRS
18
18
  import matplotlib.gridspec as gridspec
19
19
  from astropy.table import Table
@@ -23,6 +23,8 @@ import warnings
23
23
  from spectral_cube import SpectralCube
24
24
  from spectral_cube.utils import SpectralCubeWarning
25
25
  warnings.filterwarnings(action='ignore', category=SpectralCubeWarning, append=True)
26
+ from scipy.stats import mode
27
+
26
28
 
27
29
  def running_mean(x, N):
28
30
  cumsum = np.cumsum(np.insert(x, 0, 0))
@@ -38,7 +40,7 @@ def rotateImage(img, angle, pivot):
38
40
 
39
41
 
40
42
  class pymakeplots:
41
- def __init__(self,cube_flat=None,pb=None,cube=None):
43
+ def __init__(self,cube_flat=None,pb=None,cube=None,rest_value=None):
42
44
  self.galname=None
43
45
  self.gal_distance=None
44
46
  self.posang=None
@@ -66,6 +68,7 @@ class pymakeplots:
66
68
  self.bardist=None
67
69
  self.rmsfac=3
68
70
  self.restfreq=None
71
+ self.repfreq=None
69
72
  self.obj_ra=None
70
73
  self.obj_dec=None
71
74
  self.imagesize=None
@@ -78,29 +81,36 @@ class pymakeplots:
78
81
  self.spatial_trim = None
79
82
  self.maxvdisp=None
80
83
  self.cliplevel=None
84
+ self.points2plot=None
85
+ self.pointingsra=None
86
+ self.pointingsdec=None
87
+ self.pointingsdiam=None
81
88
  self.fits=False
82
89
  self.pvdthick=5.
83
90
  self.flipped=False
84
91
  self.make_square=True
85
92
  self.useallpixels = False
93
+ self.suppress_subbeam_artifacts=False
86
94
  #self.wcs=None
87
95
 
88
96
  if (cube != None)&(pb==None)&(cube_flat==None):
89
97
  # only one cube given
90
- self.input_cube_nopb(cube)
98
+ self.input_cube_nopb(cube,rest_value=rest_value)
91
99
 
92
100
  if (cube != None)&(pb!=None):
93
101
  # pbcorred cube and pb given
94
- self.input_cube_pbcorr(cube,pb)
102
+ self.input_cube_pbcorr(cube,pb,rest_value=rest_value)
95
103
 
96
104
  if (cube_flat != None)&(pb!=None):
97
105
  # flat cube and pb given
98
106
  if np.any(self.pbcorr_cube) == None: #check if the user gave all three cubes, in which case this call is redundant
99
- self.input_cube_flat(cube_flat,pb)
107
+ self.input_cube_flat(cube_flat,pb,rest_value=rest_value)
100
108
 
101
109
  if (cube != None)&(pb==None)&(cube_flat!=None):
102
110
  # pbcorred cube and flat cube given
103
- self.input_cube_pbcorr_and_flat(cube,cube_flat)
111
+ self.input_cube_pbcorr_and_flat(cube,cube_flat,rest_value=rest_value)
112
+
113
+ self.cube_for_param_guesses=self.flat_cube
104
114
 
105
115
  def vsystrans_inv(self,val):
106
116
  return val +self.vsys
@@ -124,21 +134,21 @@ class pymakeplots:
124
134
  def beam_area(self):
125
135
  return (np.pi*(self.bmaj/self.cellsize)*(self.bmin/self.cellsize))/(4*np.log(2))
126
136
 
127
- def input_cube_pbcorr(self,path_to_pbcorr_cube,path_to_pb):
137
+ def input_cube_pbcorr(self,path_to_pbcorr_cube,path_to_pb,rest_value=None):
128
138
 
129
- self.pbcorr_cube = self.read_primary_cube(path_to_pbcorr_cube)
139
+ self.pbcorr_cube = self.read_primary_cube(path_to_pbcorr_cube,rest_value=rest_value)
130
140
 
131
- pb,hdr,_= self.read_in_a_cube(path_to_pb)
141
+ pb,hdr,_= self.read_in_a_cube(path_to_pb,rest_value=rest_value)
132
142
  if self.flipped: pb=np.flip(pb,axis=2)
133
143
 
134
144
  self.flat_cube = self.pbcorr_cube*pb
135
145
 
136
146
 
137
- def input_cube_flat(self,path_to_flat_cube,path_to_pb):
147
+ def input_cube_flat(self,path_to_flat_cube,path_to_pb,rest_value=None):
138
148
 
139
- self.flat_cube = self.read_primary_cube(path_to_flat_cube)
149
+ self.flat_cube = self.read_primary_cube(path_to_flat_cube,rest_value=rest_value)
140
150
 
141
- pb,hdr,_= self.read_in_a_cube(path_to_pb)
151
+ pb,hdr,_= self.read_in_a_cube(path_to_pb,rest_value=rest_value)
142
152
  if self.flipped: pb=np.flip(pb,axis=2)
143
153
 
144
154
  self.pbcorr_cube = self.flat_cube.copy()*0.0
@@ -146,35 +156,41 @@ class pymakeplots:
146
156
 
147
157
 
148
158
 
149
- def input_cube_nopb(self,path_to_cube):
159
+ def input_cube_nopb(self,path_to_cube,rest_value=None):
150
160
 
151
- self.pbcorr_cube = self.read_primary_cube(path_to_cube)
161
+ self.pbcorr_cube = self.read_primary_cube(path_to_cube,rest_value=rest_value)
152
162
 
153
163
  self.flat_cube = self.pbcorr_cube
154
164
 
155
165
 
156
166
 
157
- def input_cube_pbcorr_and_flat(self,path_to_pbcorr_cube,path_to_flat_cube):
167
+ def input_cube_pbcorr_and_flat(self,path_to_pbcorr_cube,path_to_flat_cube,rest_value=None):
168
+
169
+ self.pbcorr_cube = self.read_primary_cube(path_to_pbcorr_cube,rest_value=rest_value)
158
170
 
159
- self.pbcorr_cube = self.read_primary_cube(path_to_pbcorr_cube)
171
+ self.flat_cube,hdr,_ = self.read_in_a_cube(path_to_flat_cube,rest_value=rest_value)
160
172
 
161
- self.flat_cube,hdr,_ = self.read_in_a_cube(path_to_flat_cube)
162
173
  if self.flipped: self.flat_cube=np.flip(self.flat_cube,axis=2)
163
174
 
164
175
  def smooth_mask(self,cube):
165
176
  """
166
177
  Apply a Gaussian blur, using sigma = 4 in the velocity direction (seems to work best), to the uncorrected cube.
167
- The mode 'nearest' seems to give the best results.
168
178
  :return: (ndarray) mask to apply to the un-clipped cube
169
179
  """
170
180
  sigma = 1.5 * self.bmaj / self.cellsize
171
181
  smooth_cube = ndimage.uniform_filter(cube, size=[sigma, sigma,4], mode='constant') # mode='nearest'
172
182
  newrms= self.rms_estimate(smooth_cube,0,1)
173
- self.cliplevel=newrms*self.rmsfac
174
- mask=(smooth_cube > self.cliplevel)
175
- # print("Clip level:",((3e20*1.6014457E-20*91.9)/(self.bmaj*self.bmin))*self.cliplevel*self.dv)
176
- # import ipdb
177
- # ipdb.set_trace()
183
+ self.cliplevel=self.rms*self.rmsfac
184
+ self.maskcliplevel=newrms*self.rmsfac
185
+ mask=(smooth_cube > self.maskcliplevel)
186
+
187
+ if self.suppress_subbeam_artifacts:
188
+ label,cnt=ndimage.label(mask)#.sum(axis=2))
189
+ hist,lab=np.histogram(label,bins=np.arange(cnt+1))
190
+ beampix=(self.bmaj*self.bmin)/(self.cellsize**2)
191
+ for thelabel in lab[0:-1][hist<(beampix*self.suppress_subbeam_artifacts)]:
192
+ mask[label == thelabel]=False
193
+
178
194
  return mask
179
195
 
180
196
 
@@ -302,7 +318,8 @@ class pymakeplots:
302
318
  return np.nanstd(cube[quarterx*1:3*quarterx,1*quartery:3*quartery,chanstart:chanend])
303
319
 
304
320
  def get_header_coord_arrays(self,hdr):
305
-
321
+
322
+
306
323
  cd1=self.spectralcube.wcs.pixel_scale_matrix[0,0]*3600
307
324
  cd2=self.spectralcube.wcs.pixel_scale_matrix[1,1]*3600
308
325
  x1=((np.arange(1,hdr['NAXIS1']+1)-(hdr['NAXIS1']//2))*cd1)# + hdr['CRVAL1']
@@ -311,37 +328,41 @@ class pymakeplots:
311
328
  v1=self.spectralcube.spectral_axis.value
312
329
 
313
330
  cd3= np.median(np.diff(v1))
331
+
314
332
 
315
333
  return x1,y1,v1,np.abs(cd1),cd3
316
334
 
317
- def read_in_a_cube(self,path):
318
- self.spectralcube=SpectralCube.read(path).with_spectral_unit(u.km/u.s, velocity_convention='radio')#, rest_value=self.restfreq)
335
+ def read_in_a_cube(self,path,rest_value=None,primary=False):
336
+
337
+ scube=SpectralCube.read(path).with_spectral_unit(u.km/u.s, velocity_convention='radio',rest_value=rest_value)
319
338
 
320
- hdr=self.spectralcube.header
321
- cube = np.squeeze(self.spectralcube.filled_data[:,:,:].T).value #squeeze to remove singular stokes axis if present
339
+ hdr=scube.header
340
+ cube = np.squeeze(scube.filled_data[:,:,:].T).value #squeeze to remove singular stokes axis if present
322
341
  cube[np.isfinite(cube) == False] = 0.0
323
342
  try:
324
- beamtab=self.spectralcube.beam
343
+ beamtab=scube.beam
325
344
  except:
326
345
  try:
327
- beamtab=self.spectralcube.beams[np.floor(self.spectralcube.beams.size/2).astype(int)]
346
+ beamtab=scube.beams[np.floor(scube.beams.size/2).astype(int)]
328
347
  except:
329
348
  #try flipping them
330
349
  try:
331
- beamvals=[self.spectralcube.header['bmaj'],self.spectralcube.header['bmin']]
350
+ beamvals=[scube.header['bmaj'],scube.header['bmin']]
332
351
  beamtab=Beam(major=np.max(beamvals)*u.deg,minor=np.min(beamvals)*u.deg,pa=self.spectralcube.header['bpa']*u.deg)
333
352
  except:
334
- beamtab=False
335
-
353
+ beamtab=False
354
+ if primary:
355
+ self.spectralcube=scube
356
+ self.repfreq=np.median(self.spectralcube.with_spectral_unit(u.GHz).spectral_axis)
336
357
  return cube, hdr, beamtab
337
358
 
338
359
 
339
360
 
340
- def read_primary_cube(self,cube):
361
+ def read_primary_cube(self,cube,rest_value=None):
341
362
 
342
363
  ### read in cube ###
343
- datacube,hdr,beam = self.read_in_a_cube(cube)
344
-
364
+ datacube,hdr,beam = self.read_in_a_cube(cube,rest_value=rest_value,primary=True)
365
+
345
366
  self.bmaj=beam.major.to(u.arcsec).value
346
367
  self.bmin=beam.minor.to(u.arcsec).value
347
368
  self.bpa=beam.pa.value
@@ -351,15 +372,9 @@ class pymakeplots:
351
372
  self.galname=hdr['OBJECT']
352
373
  except:
353
374
  self.galname="Galaxy"
354
-
355
-
356
-
375
+
357
376
  self.xcoord,self.ycoord,self.vcoord,self.cellsize,self.dv = self.get_header_coord_arrays(hdr)
358
- #breakpoint()
359
-
360
-
361
377
 
362
-
363
378
  if self.dv < 0:
364
379
  datacube = np.flip(datacube,axis=2)
365
380
  self.dv*=(-1)
@@ -369,7 +384,15 @@ class pymakeplots:
369
384
 
370
385
  self.rms= self.rms_estimate(datacube,self.linefree_chans_start,self.linefree_chans_end)
371
386
  return datacube
372
-
387
+
388
+ def calc_offset(self,ra,dec):
389
+ refpos=SkyCoord(ra,dec)
390
+ xpix,ypix=self.spectralcube.wcs.celestial.world_to_pixel(refpos)
391
+ #self.xcentpix,self.ycentpix= xpix,ypix
392
+ xoffsetarc=np.interp(xpix,np.arange(self.xcoord.size),self.xcoord)
393
+ yoffsetarc=np.interp(ypix,np.arange(self.ycoord.size),self.ycoord)
394
+ return xpix,ypix,xoffsetarc,yoffsetarc
395
+
373
396
  def prepare_cubes(self):
374
397
 
375
398
  self.centskycoord=self.spectralcube.wcs.celestial.pixel_to_world(self.xcoord.size//2,self.ycoord.size//2).transform_to('icrs')
@@ -380,12 +403,19 @@ class pymakeplots:
380
403
  if self.obj_dec == None:
381
404
  self.obj_dec=self.y_skycent
382
405
 
383
- refpos=SkyCoord(self.obj_ra*u.deg,self.obj_dec*u.deg)
384
- xpix,ypix=self.spectralcube.wcs.celestial.world_to_pixel(refpos)
385
-
386
- xoffsetarc=np.interp(xpix,np.arange(self.xcoord.size),self.xcoord)
387
- yoffsetarc=np.interp(ypix,np.arange(self.ycoord.size),self.ycoord)
388
406
 
407
+ xpix,ypix,xoffsetarc,yoffsetarc=self.calc_offset(self.obj_ra*u.deg,self.obj_dec*u.deg)
408
+ self.xcentpix,self.ycentpix= xpix,ypix
409
+
410
+
411
+ ### want to overplot the pointings
412
+ if self.pointingsra != None:
413
+ pointingposes=SkyCoord(self.pointingsra,self.pointingsdec,frame='icrs',unit=(u.hourangle,u.deg))
414
+ _,_,pointoffsetx,pointoffsety=self.calc_offset(pointingposes.ra,pointingposes.dec)
415
+ self.points2plot=np.zeros((len(pointoffsetx),3))
416
+ self.points2plot[:,0]=pointoffsetx-xoffsetarc
417
+ self.points2plot[:,1]=pointoffsety-yoffsetarc
418
+ self.points2plot[:,2]=self.pointingsdiam.value
389
419
 
390
420
  self.clip_cube(xoffsetarc,yoffsetarc)
391
421
 
@@ -404,6 +434,8 @@ class pymakeplots:
404
434
  if self.all_axes_physical:
405
435
  self.xc=self.ang2kpctrans(self.xc)
406
436
  self.yc=self.ang2kpctrans(self.yc)
437
+ if self.pointingsra != None:
438
+ self.points2plot=self.ang2kpctrans(self.points2plot)
407
439
 
408
440
 
409
441
 
@@ -441,7 +473,7 @@ class pymakeplots:
441
473
  self.make_spec(axes=ax5,fits=fits)
442
474
 
443
475
  ### plotting PA on mom1
444
- ypv=np.arange(-np.max(self.yc),np.max(self.yc),20)
476
+ ypv=np.arange(-np.max(self.yc),np.max(self.yc),self.cellsize)
445
477
  xpv=ypv*0.0
446
478
  ang=self.posang
447
479
  c = np.cos(np.deg2rad(ang))
@@ -450,7 +482,6 @@ class pymakeplots:
450
482
  y2 = s*xpv + c*ypv
451
483
  ax2.scatter(0,0,facecolors='none',edgecolors='k')
452
484
  ax2.plot(x2,y2,'k--')
453
- #breakpoint()
454
485
 
455
486
  ###### make summary box
456
487
 
@@ -478,6 +509,7 @@ class pymakeplots:
478
509
 
479
510
  if pdf:
480
511
  plt.savefig(self.galname+"_allplots.pdf", bbox_inches = 'tight')
512
+ plt.close()
481
513
  else:
482
514
  plt.show()
483
515
 
@@ -561,19 +593,27 @@ class pymakeplots:
561
593
 
562
594
 
563
595
 
564
- if self.chans2do == None:
565
- # use the mask to try and guess the channels with signal.
566
- mask_cumsum=np.nancumsum((self.pbcorr_cube > self.rmsfac*self.rms).sum(axis=0).sum(axis=0))
567
- w_low,=np.where(mask_cumsum/np.max(mask_cumsum) < 0.02)
568
- w_high,=np.where(mask_cumsum/np.max(mask_cumsum) > 0.98)
596
+ if np.any(self.chans2do == None):
569
597
 
570
- if w_low.size ==0: w_low=np.array([0])
571
- if w_high.size ==0: w_high=np.array([self.vcoord.size])
572
- self.chans2do=[np.clip(np.max(w_low)-2,0,self.vcoord.size),np.clip(np.min(w_high)+2,0,self.vcoord.size)]
573
-
598
+ clip=4.0
599
+ vwidth=4000
600
+ while vwidth > 1500:
601
+ clip+=0.1
602
+ # use the mask to try and guess the channels with signal.
603
+ mask_cumsum=np.nancumsum((self.cube_for_param_guesses > clip*self.rms).sum(axis=0).sum(axis=0))
604
+ w_low,=np.where(mask_cumsum/np.nanmax(mask_cumsum) < 0.05)
605
+ w_high,=np.where(mask_cumsum/np.nanmax(mask_cumsum) > 0.95)
606
+
607
+ if w_low.size ==0: w_low=np.array([0])
608
+ if w_high.size ==0: w_high=np.array([self.vcoord.size])
609
+ #breakpoint()
610
+ self.chans2do=[np.clip(np.max(w_low)-7,0,self.vcoord.size-1),np.clip(np.min(w_high)+7,0,self.vcoord.size-1)]
611
+ vwidth=self.vcoord[self.chans2do[1]]-self.vcoord[self.chans2do[0]]
612
+ #print(clip)
613
+
574
614
  if self.vsys == None:
575
615
  # use the cube to try and guess the vsys
576
- self.vsys=((self.pbcorr_cube*(self.pbcorr_cube > self.rmsfac*self.rms)).sum(axis=0).sum(axis=0)*self.vcoord).sum()/((self.pbcorr_cube*(self.pbcorr_cube > self.rmsfac*self.rms)).sum(axis=0).sum(axis=0)).sum()
616
+ self.vsys=((self.cube_for_param_guesses*(self.cube_for_param_guesses > self.rmsfac*self.rms)).sum(axis=0).sum(axis=0)*self.vcoord).sum()/((self.cube_for_param_guesses*(self.cube_for_param_guesses > self.rmsfac*self.rms)).sum(axis=0).sum(axis=0)).sum()
577
617
 
578
618
  if self.imagesize != None:
579
619
  if np.array(self.imagesize).size == 1:
@@ -587,7 +627,7 @@ class pymakeplots:
587
627
 
588
628
  if self.spatial_trim == None:
589
629
 
590
- mom0=(self.pbcorr_cube > self.rmsfac*self.rms).sum(axis=2)
630
+ mom0=(self.cube_for_param_guesses > self.rmsfac*self.rms).sum(axis=2)
591
631
  mom0[mom0>0]=1
592
632
 
593
633
  cumulative_x = np.nancumsum(mom0.sum(axis=1),dtype=float)
@@ -637,19 +677,16 @@ class pymakeplots:
637
677
  return cb
638
678
 
639
679
 
640
- def add_beam(self,ax):
680
+ def add_beam(self,ax):
681
+ aux_tr_box = AuxTransformBox(ax.transData)
682
+
641
683
  if self.all_axes_physical:
642
- ae = AnchoredEllipse(ax.transData, width=self.ang2kpctrans(self.bmaj), height=self.ang2kpctrans(self.bmin), angle=self.bpa+90,
643
- loc='lower left', pad=0.5, borderpad=0.4,
644
- frameon=False)
684
+ aux_tr_box.add_artist(Ellipse((0, 0), width=self.ang2kpctrans(self.bmaj), height=self.ang2kpctrans(self.bmin), angle=self.bpa+90,edgecolor='black',facecolor='none',linewidth=1.5))
645
685
  else:
646
- ae = AnchoredEllipse(ax.transData, width=self.bmaj, height=self.bmin, angle=self.bpa+90,
647
- loc='lower left', pad=0.5, borderpad=0.4,
648
- frameon=False)
649
- ae.ellipse.set_edgecolor('black')
650
- ae.ellipse.set_facecolor('none')
651
- ae.ellipse.set_linewidth(1.5)
652
- ax.add_artist(ae)
686
+ aux_tr_box.add_artist(Ellipse((0, 0), width=self.bmaj, height=self.bmin, angle=self.bpa+90,edgecolor='black',facecolor='none',linewidth=1.5))
687
+ box = AnchoredOffsetbox(child=aux_tr_box, loc='lower left', pad=0.5, borderpad=0.4,frameon=False)
688
+ ax.add_artist(box)
689
+
653
690
 
654
691
 
655
692
 
@@ -701,6 +738,29 @@ class pymakeplots:
701
738
 
702
739
 
703
740
  self.add_beam(ax1)
741
+
742
+ if np.any(self.points2plot != None):
743
+ naca=0
744
+ nalma=0
745
+ for xp,yp,diam in self.points2plot:
746
+ if diam/np.min(self.points2plot[:,2])>1.7:
747
+ ls=':' #aca
748
+ if naca ==0:
749
+ label='ACA'
750
+ else:
751
+ label=None
752
+ naca+=1
753
+ else:
754
+ ls='--' #12m
755
+ if nalma ==0:
756
+ label='12m'
757
+ else:
758
+ label=None
759
+ nalma+=1
760
+ circle2 = plt.Circle((xp, yp), diam/2., color='k',ls=ls, fill=False,alpha=0.2,label=label)
761
+ ax1.add_patch(circle2)
762
+ ax1.legend(frameon=False,loc='upper left',fontsize='x-small',markerscale=0.5)
763
+
704
764
  if self.make_square:
705
765
  ax1.set_xlim(np.min([self.xc[0],self.yc[0]]),np.max([self.xc[-1],self.yc[-1]]))
706
766
  ax1.set_ylim(np.min([self.xc[0],self.yc[0]]),np.max([self.xc[-1],self.yc[-1]]))
@@ -725,7 +785,7 @@ class pymakeplots:
725
785
  mom1=mom0.copy()*np.nan
726
786
  mom1[mom0 != 0.0] = (((self.pbcorr_cube_trim*self.mask_trim)*self.vcoord_trim).sum(axis=2))[mom0 != 0.0]/mom0[mom0 != 0.0]
727
787
 
728
-
788
+
729
789
  vticks=np.linspace((-1)*np.ceil(np.max(np.abs(self.vcoord_trim-self.vsys))/10.)*10.,np.ceil(np.max(np.abs(self.vcoord_trim-self.vsys))/10.)*10.,5)
730
790
 
731
791
  im1=ax1.contourf(self.xc,self.yc,mom1.T-self.vsys,levels=self.vcoord_trim-self.vsys,cmap=sauron,vmin=vticks[0],vmax=vticks[-1])
@@ -771,12 +831,13 @@ class pymakeplots:
771
831
  mom2[i,j]=np.sqrt(np.sum(np.abs(self.pbcorr_cube_trim[i,j,:]*self.mask_trim[i,j,:]) * (self.vcoord_trim - mom1[i,j]) ** 2, axis=0) / np.sum(abs(self.pbcorr_cube_trim[i,j]*self.mask_trim[i,j,:]), axis=0))
772
832
 
773
833
  if self.maxvdisp == None:
774
- self.maxvdisp=np.ceil(np.clip(np.nanstd(mom2)*4,0,np.nanmax(mom2))/10.)*10.
775
-
776
-
777
-
834
+ self.maxvdisp=np.ceil(np.clip(np.nanstd(mom2)*4,20,np.nanmax(mom2))/10.)*10.
835
+ else:
836
+ self.maxvdisp=np.ceil(np.clip(np.nanstd(mom2)*4,20,self.maxvdisp)/10.)*10.
837
+ if np.isfinite(self.maxvdisp)==False:
838
+ self.maxvdisp=50.
839
+ #breakpoint()
778
840
  mom2levs=np.linspace(0,self.maxvdisp,10)
779
-
780
841
  im1=ax1.contourf(self.xc,self.yc,mom2.T,levels=mom2levs,cmap=sauron,vmax=self.maxvdisp)
781
842
 
782
843
  if self.all_axes_physical:
@@ -796,7 +857,7 @@ class pymakeplots:
796
857
  vticks=np.arange(0,5)*dvticks
797
858
 
798
859
  cb=self.colorbar(im1,ticks=vticks)
799
- cb.set_label("$\sigma_{obs}$ (km s$^{-1}$)")
860
+ cb.set_label('$\\sigma_{obs}$ (km s$^{-1}$)')
800
861
 
801
862
 
802
863
  self.add_beam(ax1)
@@ -860,8 +921,8 @@ class pymakeplots:
860
921
  newhdu.header['CUNIT2']=self.spectralcube.header['CUNIT2']
861
922
  newhdu.header['BMAJ']=self.bmaj/3600.
862
923
  newhdu.header['BMIN']=self.bmin/3600.
863
- newhdu.header['BPA']=self.bpa/3600.
864
- newhdu.header['MOMCLIP']=(self.cliplevel, self.bunit+' km/s')
924
+ newhdu.header['BPA']=self.bpa
925
+ newhdu.header['MOMCLIP']=(self.maskcliplevel, self.bunit+' km/s')
865
926
  newhdu.header['VSYS']=(self.vsys,'km/s')
866
927
  newhdu.header['comment'] = 'Moment map created with pymakeplots'
867
928
 
@@ -881,7 +942,7 @@ class pymakeplots:
881
942
  filename=self.galname+"_pvd.fits"
882
943
  else:
883
944
  filename=self.fits+"_pvd.fits"
884
-
945
+
885
946
  newhdu = fits.PrimaryHDU(pvd)
886
947
  newhdu.header['CRPIX1']=1
887
948
  newhdu.header['CRVAL1']=xx[0]
@@ -895,10 +956,10 @@ class pymakeplots:
895
956
  newhdu.header['CUNIT2']='km/s'
896
957
  newhdu.header['BMAJ']=self.bmaj/3600.
897
958
  newhdu.header['BMIN']=self.bmin/3600.
898
- newhdu.header['BPA']=self.bpa/3600.
959
+ newhdu.header['BPA']=self.bpa
899
960
  newhdu.header['PVDANGLE']=(self.posang,'deg')
900
961
  newhdu.header['PVDTHICK']=(self.pvdthick,'pixels')
901
- newhdu.header['MOMCLIP']=(self.cliplevel, self.bunit+' km/s')
962
+ newhdu.header['MOMCLIP']=(self.maskcliplevel, self.bunit+' km/s')
902
963
  newhdu.header['VSYS']=(self.vsys,'km/s')
903
964
  newhdu.header['comment'] = 'Moment map created with pymakeplots'
904
965
  newhdu.header['BUNIT'] = self.bunit+' km/s'
@@ -906,7 +967,7 @@ class pymakeplots:
906
967
  newhdu.writeto(filename,overwrite=True)
907
968
 
908
969
  def make_pvd(self,axes=None,fits=False,pdf=False):
909
-
970
+ self.fits=fits
910
971
  if np.any(self.xc) == None:
911
972
  self.prepare_cubes()
912
973
 
@@ -921,31 +982,67 @@ class pymakeplots:
921
982
  if self.posang==None:
922
983
  # try fitting the moment one to get the kinematic pa
923
984
  if not self.silent: print("No position angle given, estimating using the observed moment one.")
924
- mom0=(self.pbcorr_cube_trim*self.mask_trim).sum(axis=2)
985
+ mom0=(self.flat_cube_trim*self.mask_trim).sum(axis=2)
925
986
  mom1=mom0.copy()*np.nan
926
- mom1[mom0 != 0.0] = (((self.pbcorr_cube_trim*self.mask_trim)*self.vcoord_trim).sum(axis=2))[mom0 != 0.0]/mom0[mom0 != 0.0]
927
- mom1=mom1.T
987
+ mom1[mom0 != 0.0] = (((self.flat_cube_trim*self.mask_trim)*self.vcoord_trim).sum(axis=2))[mom0 != 0.0]/mom0[mom0 != 0.0]
988
+ mom1=mom1.T
928
989
 
929
-
930
- # if the cube is small, use it directly to estimate posang. If its large, then interpolate down to keep runtime low.
931
- if (self.pbcorr_cube_trim[:,:,0].size < 50*50) or (self.useallpixels):
932
- xv, yv = np.meshgrid(self.xc,self.yc)
933
- x,y,v = xv[np.isfinite(mom1)],yv[np.isfinite(mom1)],mom1[np.isfinite(mom1)]
934
- else:
935
- print("Downsampling the observed moment one in PA estimate for speed. Set `useallpixels` to override.")
936
- mom1[np.isfinite(mom1) == False] = self.vsys
937
- interper = interpolate.interp2d(self.xc,self.yc,mom1-self.vsys,bounds_error=False,fill_value=np.nan)
938
- x=np.linspace(np.min(self.xc),np.max(self.xc),50)
939
- y=np.linspace(np.min(self.yc),np.max(self.yc),50)
940
- v= interper(x,y)
941
- xv, yv = np.meshgrid(x,y)
942
- x,y,v = xv.flatten(),yv.flatten(),v.flatten()
990
+ # ### sigma clip
991
+ # mom1[np.abs(mom1/np.nanstd(mom1))>5]=np.nan
992
+
993
+ ### select largest contigious structure
994
+ label,cnt=ndimage.label(self.mask_trim.sum(axis=2))
995
+ mode_label= mode(label[label>0])
996
+
997
+ hist,lab=np.histogram(label[label>0],bins=np.arange(cnt+1)+1)
998
+ beampix=(self.bmaj*self.bmin)/(self.cellsize**2)
999
+
1000
+
1001
+ if len(lab[0:-1][hist<beampix]) != len(lab[0:-1]):
943
1002
 
944
- self.posang,_,_ = fit_kinematic_pa(x[np.isfinite(v)],y[np.isfinite(v)],v[np.isfinite(v)],nsteps=36,plot=False,quiet=True)
1003
+ ## remove beam size artifacts
1004
+ for thelabel in lab[0:-1][hist<beampix]:
1005
+ mom1[label.T == thelabel]=np.nan
1006
+
1007
+ ## keep biggest structures that contain >80% of remaining pixels
1008
+ remainlab=lab[0:-1][hist>beampix]
1009
+ remainhist=hist[hist>beampix]
1010
+ st=np.argsort(remainhist)
1011
+ for thelabel in remainlab[st][np.nancumsum(remainhist[st]/np.sum(remainhist)) < 0.2]:
1012
+ mom1[label.T == thelabel]=np.nan
1013
+
1014
+ else:
1015
+ mom1[label.T != mode_label.mode]=np.nan
945
1016
 
1017
+ #self.useallpixels=True
1018
+
1019
+ ### remove median
1020
+ mom1-=np.nanmedian(mom1)
1021
+
1022
+
1023
+
1024
+ #breakpoint()
1025
+ # if the cube is small, use it directly to estimate posang. If its large, then interpolate down to keep runtime low.
1026
+ # if (self.pbcorr_cube_trim[:,:,0].size < 50*50) or (self.useallpixels):
1027
+ xv, yv = np.meshgrid(self.xc,self.yc)
1028
+ x,y,v = xv[np.isfinite(mom1)],yv[np.isfinite(mom1)],mom1[np.isfinite(mom1)]
1029
+ # else:
1030
+ # print("Downsampling the observed moment one in PA estimate for speed. Set `useallpixels` to override.")
1031
+ #mom1[np.isfinite(mom1) == False] = self.vsys
1032
+ #breakpoint()
1033
+ # interper = interpolate.RegularGridInterpolator((self.xc,self.yc),(mom1-self.vsys).T,bounds_error=False,fill_value=np.nan)
1034
+ # x=np.linspace(np.min(self.xc),np.max(self.xc),self.xc.size//2)
1035
+ # y=np.linspace(np.min(self.yc),np.max(self.yc),self.yc.size//2)
1036
+ # xv, yv = np.meshgrid(x,y)
1037
+ # v= interper((xv,yv))
1038
+ # x,y,v = xv.flatten(),yv.flatten(),v.flatten()
1039
+
1040
+ #breakpoint()
1041
+ self.posang,_,_ = fit_kinematic_pa(x[np.isfinite(v)],y[np.isfinite(v)],v[np.isfinite(v)],nsteps=36,plot=False,quiet=True)
1042
+
946
1043
  if np.sin(np.deg2rad((self.posang+45)*2)) > 0:
947
1044
  # do y axis cut
948
- if np.nanmean(mom1[self.yc > 0,:]) > np.nanmean(mom1[self.yc < 0,:]):
1045
+ if np.nansum(mom1[self.yc > 0,:])/(mom1[self.yc > 0,:]).size > np.nansum(mom1[self.yc < 0,:])/(mom1[self.yc < 0,:]).size:
949
1046
  # posang should be gt 180
950
1047
  if self.posang < 180: self.posang += 180
951
1048
  else:
@@ -953,21 +1050,20 @@ class pymakeplots:
953
1050
  if self.posang > 180: self.posang -= 180
954
1051
  else:
955
1052
  # do x axis cut
956
- if np.nanmean(mom1[:,self.xc > 0]) > np.nanmean(mom1[:,self.xc < 0]):
1053
+ if np.nansum(mom1[:,self.xc > 0])/(mom1[:,self.xc > 0]).size > np.nansum(mom1[:,self.xc < 0])/(mom1[:,self.xc < 0]).size:
957
1054
  # posang should be gt 180
958
1055
  if self.posang < 180: self.posang += 180
959
1056
  else:
960
1057
  # posang should be lt 180
961
1058
  if self.posang > 180: self.posang -= 180
962
1059
  if not self.silent: print("PA estimate (degrees): ",np.round(self.posang,1))
963
-
964
-
1060
+
965
1061
  centpix_x=np.where(np.isclose(self.xc,0.0,atol=self.cellsize/1.9))[0]
966
1062
  centpix_y=np.where(np.isclose(self.yc,0.0,atol=self.cellsize/1.9))[0]
967
- #breakpoint()
1063
+
968
1064
 
969
1065
 
970
- rotcube= rotateImage(self.pbcorr_cube_trim*self.mask_trim,90-self.posang,[centpix_y[0],centpix_x[0]])
1066
+ rotcube= rotateImage(self.pbcorr_cube_trim*self.mask_trim,90-self.posang,[centpix_x[0],centpix_y[0]])
971
1067
 
972
1068
 
973
1069
  pvd=rotcube[:,np.array(rotcube.shape[1]//2-self.pvdthick).astype(int):np.array(rotcube.shape[1]//2+self.pvdthick).astype(int),:].sum(axis=1)
@@ -998,34 +1094,44 @@ class pymakeplots:
998
1094
  newcmp = ListedColormap(oldcmp(np.linspace(0.15, 1, 256)))
999
1095
 
1000
1096
 
1097
+ if np.nanmax(pvd) < self.cliplevel:
1098
+ contour_levels=np.linspace(np.nanmax(pvd)/2.,np.nanmax(pvd),10)
1099
+ if np.sum(contour_levels)==0:
1100
+ contour_levels=np.array([self.cliplevel,self.cliplevel+0.1])
1101
+ else:
1102
+ contour_levels=np.linspace(self.cliplevel,np.nanmax(pvd),10)
1001
1103
 
1002
- axes.contourf(pvdaxis,vaxis,pvd.T,levels=np.linspace(self.cliplevel,np.nanmax(pvd),10),cmap=newcmp)
1003
- axes.contour(pvdaxis,vaxis,pvd.T,levels=np.linspace(self.cliplevel,np.nanmax(pvd),10),colors='black')
1104
+ axes.contourf(pvdaxis,vaxis,pvd.T,levels=contour_levels,cmap=newcmp)
1105
+ axes.contour(pvdaxis,vaxis,pvd.T,levels=contour_levels,colors='black')
1004
1106
 
1005
1107
  if self.all_axes_physical:
1006
1108
  axes.set_xlabel('Offset (kpc)')
1109
+ secaxy = axes.secondary_xaxis('top', functions=(self.ang2kpctrans_inv, self.ang2kpctrans))
1110
+ secaxy.set_xlabel('Offset (")')
1007
1111
  else:
1008
1112
  axes.set_xlabel('Offset (")')
1009
-
1113
+ secaxy = axes.secondary_xaxis('top', functions=(self.ang2kpctrans, self.ang2kpctrans_inv))
1114
+ secaxy.set_xlabel('Offset (kpc)')
1115
+
1010
1116
  axes.set_ylabel('Velocity (km s$^{-1}$)')
1011
1117
 
1012
- secaxy = axes.secondary_xaxis('top', functions=(self.ang2kpctrans, self.ang2kpctrans_inv))
1013
- secaxy.set_xlabel('Offset (kpc)')
1118
+
1014
1119
 
1015
1120
  secax = axes.secondary_yaxis('right', functions=(self.vsystrans, self.vsystrans_inv))
1016
1121
  secax.set_ylabel(r'V$_{\rm offset}$ (km s$^{-1}$)')
1017
1122
 
1018
- anchored_text = AnchoredText("PA: "+str(round(self.posang,1))+"$^{\circ}$", loc=loc1,frameon=False)
1123
+ anchored_text = AnchoredText("PA: "+str(round(self.posang,1))+'$^{\\circ}$', loc=loc1,frameon=False)
1019
1124
  axes.add_artist(anchored_text)
1020
1125
 
1021
1126
  if self.gal_distance != None and not self.all_axes_physical:
1022
1127
  self.scalebar(axes,loc=loc2)
1023
-
1128
+
1024
1129
  if self.fits:
1025
1130
  self.write_pvd_fits(pvdaxis,vaxis,pvd.T)
1026
1131
 
1027
1132
  if pdf:
1028
1133
  plt.savefig(self.galname+"_pvd.pdf", bbox_inches = 'tight')
1134
+ plt.close()
1029
1135
  else:
1030
1136
  if not outsideaxis: plt.show()
1031
1137
 
@@ -1097,6 +1203,7 @@ class pymakeplots:
1097
1203
 
1098
1204
  if pdf:
1099
1205
  plt.savefig(self.galname+"_spec.pdf", bbox_inches = 'tight')
1206
+ plt.close()
1100
1207
  else:
1101
1208
  if not outsideaxis: plt.show()
1102
1209
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pymakeplots
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Home-page: https://github.com/TimothyADavis/pymakeplots
5
5
  Author: Timothy A. Davis
6
6
  Author-email: DavisT@cardiff.ac.uk
@@ -11,6 +11,13 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Operating System :: OS Independent
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE.md
14
+ Requires-Dist: numpy
15
+ Requires-Dist: matplotlib>3.3.1
16
+ Requires-Dist: scipy>=1.14.0
17
+ Requires-Dist: astropy
18
+ Requires-Dist: spectral-cube
19
+ Requires-Dist: radio-beam
20
+ Requires-Dist: pafit
14
21
 
15
22
  [![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-382/) [![PyPI version](https://badge.fury.io/py/pymakeplots.svg)](https://badge.fury.io/py/pymakeplots)
16
23
 
@@ -1,6 +1,6 @@
1
1
  numpy
2
2
  matplotlib>3.3.1
3
- scipy
3
+ scipy>=1.14.0
4
4
  astropy
5
5
  spectral-cube
6
6
  radio-beam
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
 
7
7
  setup(name='pymakeplots',
8
- version='0.2.0',
8
+ version='0.2.2',
9
9
  description='',
10
10
  url='https://github.com/TimothyADavis/pymakeplots',
11
11
  author='Timothy A. Davis',
@@ -17,7 +17,7 @@ setup(name='pymakeplots',
17
17
  install_requires=[
18
18
  'numpy',
19
19
  'matplotlib>3.3.1',
20
- 'scipy',
20
+ 'scipy>=1.14.0',
21
21
  'astropy',
22
22
  'spectral-cube',
23
23
  'radio-beam',
File without changes
File without changes
File without changes