def makeRGBImages(simulation, wavelengthTuples=None, *, fileType="total", fromPercentile=30, toPercentile=100, outDirPath=None): # get the (instrument, output file path) tuples to be handled instr_paths = sm.instrumentOutFilePaths(simulation, fileType + ".fits") if len(instr_paths) < 1: return # convert the wavelength tuples argument into a list of names and wavelength tuples if wavelengthTuples is None: name_waves = [("", None)] elif isinstance(wavelengthTuples, (tuple, list)): name_waves = [("_" + str(index + 1), wavetuple) for index, wavetuple in enumerate(wavelengthTuples)] elif isinstance(wavelengthTuples, dict): name_waves = [("" if not name else "_" + name, wavetuple) for name, wavetuple in wavelengthTuples.items()] else: raise ValueError("Invalid wavelengthTuples type") # loop over all wavelength tuples for name, wavetuple in name_waves: # determine the appropriate pixel range for ALL images ranges = [] for instrument, filepath in instr_paths: image = RGBImage( filepath, frameIndices=instrument.wavelengthIndices(wavetuple)) ranges += list( image.percentilePixelRange(fromPercentile, toPercentile)) rmin = min(ranges) rmax = max(ranges) # create an RGB file for each output file for instrument, filepath in instr_paths: image = RGBImage( filepath, frameIndices=instrument.wavelengthIndices(wavetuple)) image.setRange(rmin, rmax) image.applyLog() image.applyCurve() # determine output file path saveFilePath = ut.savePath(filepath.with_name(filepath.stem + name + ".png"), ".png", outDirPath=outDirPath) image.saveTo(saveFilePath) logging.info("Created {}".format(saveFilePath))
def makeConvolvedRGBImages(simulation, contributions, name="", *, fileType="total", decades=3, fmax=None, fmin=None, outDirPath=None): # get the (instrument, output file path) tuples to be handled instr_paths = sm.instrumentOutFilePaths(simulation, fileType+".fits") if len(instr_paths) > 0: name = "" if not name else "_" + name sbunit = sm.unit("MJy/sr") # construct an image object with integrated color channels for each output file # and keep track of the largest surface brightness value (in MJy/sr) in each of the images images = [] fmaxes = [] for instrument, filepath in instr_paths: logging.info("Convolving for RGB image {}{}".format(filepath.stem, name)) # get the data cube in its intrinsic units cube = sm.loadFits(filepath) # initialize an RGB frame dataRGB = np.zeros( (cube.shape[0], cube.shape[1], 3) ) << sbunit # add color for each filter for band,w0,w1,w2 in contributions: # convolve and convert to per frequency units data = band.convolve(instrument.wavelengths(), cube, flavor=sbunit, numWavelengths=10) dataRGB[:,:,0] += w0*data dataRGB[:,:,1] += w1*data dataRGB[:,:,2] += w2*data # construct the image object images.append(vis.RGBImage(dataRGB.value)) fmaxes.append(dataRGB.max()) # determine the appropriate pixel range for all output images fmax = max(fmaxes) if fmax is None else fmax << sbunit fmin = fmax/10**decades if fmin is None else fmin << sbunit # create an RGB file for each output file for (instrument, filepath), image in zip(instr_paths,images): image.setRange(fmin.value, fmax.value) image.applyLog() image.applyCurve() # determine output file path saveFilePath = ut.savePath(filepath.with_name(filepath.stem+name+".png"), ".png", outDirPath=outDirPath) image.saveTo(saveFilePath) logging.info("Created convolved RGB image file {}".format(saveFilePath)) # return the surface brightness range used for these images return fmin,fmax
def makeWavelengthMovie(simulation, *, maxPercentile=100, minPercentile=10, decades=None, renormalize=False, outDirPath=None, outFileName=None, outFilePath=None, rate=7): # get the list of instruments and corresponding output file paths instrA, sedPaths = zip(*sm.instrumentOutFilePaths(simulation, "sed.dat")) instrB, cubPaths = zip( *sm.instrumentOutFilePaths(simulation, "total.fits")) instrA = instrA[:3] instrB = instrB[:3] sedPaths = sedPaths[:3] cubPaths = cubPaths[:3] if len(instrA) < 1 or len(instrA) != len(instrB) \ or any([ a.name() != b.name() for a,b in zip(instrA,instrB) ]): return instruments = instrA # get the wavelength grid for the first instrument (assumed to be the same for all instruments) wavelengths = instruments[0].wavelengths() if len(wavelengths) < 3: return nlambda = len(wavelengths) # load the data logging.info("Creating movie for {} ({} wavelengths and {} instruments)..." \ .format(instruments[0].prefix(), nlambda, len(instruments))) sedData = [ sm.loadColumns(sedPath, "total flux")[0] for sedPath in sedPaths ] cubData = [sm.loadFits(cubPath).value for cubPath in cubPaths] # determine the shape (assume that frames in all fits files have the same shape) imgShape = cubData[0].shape[:2] sedShape = (len(cubData) * imgShape[0], max(imgShape[1] // 2, 300)) totalShape = (sedShape[0], imgShape[1] + sedShape[1]) # determine the global surface brightness range fmax = max( [np.percentile(np.unique(cube), maxPercentile) for cube in cubData]) if decades is None: fmin = min([ np.percentile(np.unique(cube), minPercentile) for cube in cubData ]) else: fmin = fmax / 10**decades # determine the global integrated flux range Fmax = max([sed.max() for sed in sedData]) Fmin = Fmax * fmin / fmax # open the movie file defSaveFilePath = sedPaths[0].with_name(instruments[0].prefix() + "_wavemovie.mp4") saveFilePath = ut.savePath(defSaveFilePath, (".mp4", ), outDirPath=outDirPath, outFileName=outFileName, outFilePath=outFilePath) movie = MovieFile(saveFilePath, shape=totalShape, rate=rate) # for each wavelength, construct and add a movie frame for frame in range(nlambda): logging.info(" adding frame " + str(frame + 1) + "/" + str(nlambda) + "...") # determine the surface brightness range for this frame, if needed if renormalize: fmax = max([ np.percentile(np.unique(cube[:, :, frame]), maxPercentile) for cube in cubData ]) if decades is None: fmin = min([ np.percentile(np.unique(cube[:, :, frame]), minPercentile) for cube in cubData ]) else: fmin = fmax / 10**decades # assemble the top panel image = None for cube in cubData: im = RGBImage( np.dstack((cube[:, :, frame], cube[:, :, frame], cube[:, :, frame]))) im.setRange(fmin, fmax) im.applyLog() im.applyColorMap("gnuplot2") if image == None: image = im else: image.addRight(im) # plot the seds in the bottom panel dpi = 100 figure = Figure(dpi=dpi, figsize=(sedShape[0] / dpi, sedShape[1] / dpi), facecolor='w', edgecolor='w') canvas = FigureCanvasAgg(figure) ax = figure.add_subplot(111) colors = ('r', 'g', 'b') for sed, instrument, color in zip(sedData, instruments, colors): ax.loglog(wavelengths.value, sed.value, color=color, label=instrument.name()) ax.axvline(wavelengths[frame].value, color='m') ax.set_ylabel( sm.latexForSpectralFlux(sedData[0]) + sm.latexForUnit(sedData[0])) ax.set_ylim(Fmin.value / 1.1, Fmax.value * 1.1) ax.legend(loc='lower right', title=sm.latexForWavelength(wavelengths) \ + r"$={0:.4g}\,$".format(wavelengths[frame].value)+sm.latexForUnit(wavelengths)) canvas.draw() im = RGBImage(figure) image.addBelow(im) # add the frame to the movie movie.addFrame(image) # close the movie file movie.close() logging.info("Created {}".format(saveFilePath))
def plotSeds(simulation, minWavelength=None, maxWavelength=None, decades=None, *, outDirPath=None, outFileName=None, outFilePath=None, figSize=(8, 6), interactive=None): # private function to get the maximum flux within the wavelength range passed to the plotSeds function def maxFluxInRange(flux, wave): wmin = minWavelength if minWavelength is not None else wave[0] wmax = maxWavelength if maxWavelength is not None else wave[-1] mask = (wave>=wmin) & (wave<=wmax) if np.count_nonzero(mask) > 0: return flux[mask].max() else: return flux.max() # get the (instrument, output file path) tuples instr_paths = sm.instrumentOutFilePaths(simulation, "sed.dat") if len(instr_paths) < 1: return # setup the figure plt.figure(figsize=figSize) # if there is a single output file, and it has components, plot the components if len(instr_paths) == 1 and any(["transparent" in col for col in sm.getColumnDescriptions(instr_paths[0][1])]): instrument, filepath = instr_paths[0] # load the columns (we assume that all components have the same units) wave, tot, tra, dirpri, scapri, dirsec, scasec = sm.loadColumns(filepath, (0, 1, 2, 3, 4, 5, 6)) waveUnit = wave.unit fluxUnit = tot.unit fluxMax = max(maxFluxInRange(tot, wave), maxFluxInRange(tra, wave)) # plot the various components label = "{} {} ".format(instrument.prefix(), instrument.name()) plt.plot(wave.value, tot.value, color='k', ls='solid', label=label + "total") plt.plot(wave.value, tra.value, color='b', ls='dotted', label=label + "transparent") plt.plot(wave.value, (dirpri + scapri).value, color='b', ls='solid', label=label + "primary") plt.plot(wave.value, (dirsec + scasec).value, color='r', ls='solid', label=label + "secondary") # otherwise loop over all SEDs else: colors = ('r', 'g', 'b', 'c', 'm', 'y') colorindex = 0 first = True for instrument, filepath in instr_paths: # load the total flux; first time remember units; thereafter convert units wave, flux = sm.loadColumns(filepath, (0, 1)) if first: waveUnit = wave.unit fluxUnit = flux.unit fluxMax = maxFluxInRange(flux, wave) first = False else: wave <<= waveUnit flux = sm.convertToFlavor(wave, flux, fluxUnit) fluxMax = max(fluxMax, maxFluxInRange(flux, wave)) # plot plt.plot(wave.value, flux.value, color=colors[colorindex], label="{} {} total".format(instrument.prefix(), instrument.name())) # advance color index colorindex = (colorindex + 1) % len(colors) # set axis details and add a legend plt.xscale('log') plt.yscale('log') plt.xlim(_adjustWavelengthRange(plt.xlim(), waveUnit, minWavelength, maxWavelength)) if decades is not None: plt.ylim(fluxMax.value * 10 ** (-decades), fluxMax.value * 10 ** 0.2) plt.xlabel(sm.latexForWavelengthWithUnit(waveUnit), fontsize='large') plt.ylabel(sm.latexForSpectralFluxWithUnit(fluxUnit), fontsize='large') plt.legend(loc='best') # if not in interactive mode, save the figure; otherwise leave it open if not ut.interactive(interactive): # use the first instrument output path; if there are multiple instruments, remove the instrument name defSaveFilePath = instr_paths[0][1] if len(instr_paths) > 1: defSaveFilePath = defSaveFilePath.with_name(instr_paths[0][0].prefix() + "_sed.pdf") saveFilePath = ut.savePath(defSaveFilePath, (".pdf",".png"), outDirPath=outDirPath, outFileName=outFileName, outFilePath=outFilePath) plt.savefig(saveFilePath, bbox_inches='tight', pad_inches=0.25) plt.close() logging.info("Created {}".format(saveFilePath))
def plotPolarization(simulation, *, plotLinMap=True, plotDegMap=False, plotDegAvg=False, plotCirMap=False, wavelength=None, binSize=(7, 7), degreeScale=None, decades=5, outDirPath=None, figSize=(8, 6), interactive=None): # loop over all applicable instruments for instrument, filepath in sm.instrumentOutFilePaths( simulation, "stokesQ.fits"): # form the simulation/instrument name insname = "{}_{}".format(instrument.prefix(), instrument.name()) # get the file paths for the frames/data cubes filepathI = instrument.outFilePaths("total.fits")[0] filepathQ = instrument.outFilePaths("stokesQ.fits")[0] filepathU = instrument.outFilePaths("stokesU.fits")[0] filepathV = instrument.outFilePaths("stokesV.fits")[0] # load datacubes with shape (nx, ny, nlambda) Is = sm.loadFits(filepathI) Qs = sm.loadFits(filepathQ) Us = sm.loadFits(filepathU) Vs = sm.loadFits(filepathV) # load the axes grids (assuming all files have the same axes) xgrid, ygrid, wavegrid = sm.getFitsAxes(filepathI) xmin = xgrid[0].value xmax = xgrid[-1].value ymin = ygrid[0].value ymax = ygrid[-1].value extent = (xmin, xmax, ymin, ymax) # determine binning configuration binX = binSize[0] orLenX = Is.shape[0] dropX = orLenX % binX startX = dropX // 2 binY = binSize[1] orLenY = Is.shape[1] dropY = orLenY % binY startY = dropY // 2 # construct arrays with central bin positions in pixel coordinates posX = np.arange(startX - 0.5 + binX / 2.0, orLenX - dropX + startX - 0.5, binX) posY = np.arange(startY - 0.5 + binY / 2.0, orLenY - dropY + startY - 0.5, binY) # determine the appropriate wavelength index or indices if wavelength is None: indices = [0] elif wavelength == 'all': indices = range(Is.shape[2]) else: if not isinstance(wavelength, (list, tuple)): wavelength = [wavelength] indices = instrument.wavelengthIndices(wavelength) # loop over all requested wavelength indices for index in indices: wave = wavegrid[index] wavename = "{:09.4f}".format(wave.to_value(sm.unit("micron"))) wavelatex = r"$\lambda={:.4g}$".format( wave.value) + sm.latexForUnit(wave) # extract the corresponding frame, and transpose to (y,x) style for compatibility with legacy code I = Is[:, :, index].T.value Q = Qs[:, :, index].T.value U = Us[:, :, index].T.value V = Vs[:, :, index].T.value # perform the actual binning binnedI = np.zeros((len(posY), len(posX))) binnedQ = np.zeros((len(posY), len(posX))) binnedU = np.zeros((len(posY), len(posX))) binnedV = np.zeros((len(posY), len(posX))) for x in range(len(posX)): for y in range(len(posY)): binnedI[y, x] = np.sum( I[startY + binY * y:startY + binY * (y + 1), startX + binX * x:startX + binX * (x + 1)]) binnedQ[y, x] = np.sum( Q[startY + binY * y:startY + binY * (y + 1), startX + binX * x:startX + binX * (x + 1)]) binnedU[y, x] = np.sum( U[startY + binY * y:startY + binY * (y + 1), startX + binX * x:startX + binX * (x + 1)]) binnedV[y, x] = np.sum( V[startY + binY * y:startY + binY * (y + 1), startX + binX * x:startX + binX * (x + 1)]) # ----------------------------------------------------------------- # plot a linear polarization map if plotLinMap: fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize) # configure the axes ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large') ax.set_ylabel("y" + sm.latexForUnit(ygrid), fontsize='large') # determine intensity range for the background image, ignoring pixels with outrageously high flux Ib = I.copy() highmask = Ib > 1e6 * np.nanmedian(np.unique(Ib)) vmax = np.nanmax(Ib[~highmask]) Ib[highmask] = vmax vmin = vmax / 10**decades # plot the background image and the corresponding color bar normalizer = matplotlib.colors.LogNorm(vmin, vmax) cmap = plt.get_cmap('PuRd') cmap.set_under('w') backPlot = ax.imshow(Ib, norm=normalizer, cmap=cmap, extent=extent, aspect='equal', interpolation='bicubic', origin='lower') cbarlabel = sm.latexForSpectralFlux(Is) + sm.latexForUnit( Is) + " @ " + wavelatex plt.colorbar(backPlot, ax=ax).set_label(cbarlabel, fontsize='large') # compute the linear polarization degree degreeLD = np.sqrt(binnedQ**2 + binnedU**2) degreeLD[degreeLD > 0] /= binnedI[degreeLD > 0] # determine a characteristic 'high' degree of polarization in the frame # (this has to be done before degreeLD contains 'np.NaN') charDegree = np.percentile(degreeLD, 99.0) if not 0 < charDegree < 1: charDegree = np.nanmax((np.nanmax(degreeLD), 0.0001)) # remove pixels with minuscule polarization degreeLD[degreeLD < charDegree / 50] = np.NaN # determine the scaling so that the longest arrows do not to overlap with neighboring arrows if degreeScale is None: degreeScale = _roundUp(charDegree) lengthScale = 2.2 * degreeScale * max( float(len(posX)) / figSize[0], float(len(posY)) / figSize[1]) key = "{:.3g}%".format(100 * degreeScale) # compute the polarization angle angle = 0.5 * np.arctan2( binnedU, binnedQ ) # angle from North through East while looking at the sky # create the polarization vector arrays xPolarization = -degreeLD * np.sin( angle ) #For angle = 0: North & x=0, For angle = 90deg: West & x=-1 yPolarization = degreeLD * np.cos( angle ) #For angle = 0: North & y=1, For angle = 90deg: West & y=0 # plot the vector field (scale positions to data coordinates) X, Y = np.meshgrid(xmin + posX * (xmax - xmin) / orLenX, ymin + posY * (ymax - ymin) / orLenY) quiverPlot = ax.quiver(X, Y, xPolarization, yPolarization, pivot='middle', units='inches', angles='xy', scale=lengthScale, scale_units='inches', headwidth=0, headlength=1, headaxislength=1, minlength=0.8, width=0.02) ax.quiverkey(quiverPlot, 0.85, 0.02, degreeScale, key, coordinates='axes', labelpos='E') # if not in interactive mode, save the figure; otherwise leave it open if not ut.interactive(interactive): saveFilePath = ut.savePath( filepath, ".pdf", outDirPath=outDirPath, outFileName="{}_{}_pollinmap.pdf".format( insname, wavename)) plt.savefig(saveFilePath, bbox_inches='tight', pad_inches=0.25) plt.close() logging.info("Created {}".format(saveFilePath)) # ----------------------------------------------------------------- # plot a linear polarization degree map if plotDegMap: fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize) # configure the axes ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large') ax.set_ylabel("y" + sm.latexForUnit(ygrid), fontsize='large') # calculate polarization degree for each pixel, in percent # set degree to zero for pixels with very low intensity cutmask = I < (np.nanmax( I[I < 1e6 * np.nanmedian(np.unique(I))]) / 10**decades) degreeHD = np.sqrt(Q**2 + U**2) degreeHD[~cutmask] /= I[~cutmask] degreeHD[cutmask] = 0 degreeHD *= 100 # plot the image and the corresponding color bar vmax = degreeScale if degreeScale is not None else np.percentile( degreeHD, 99) normalizer = matplotlib.colors.Normalize(vmin=0, vmax=vmax) backPlot = ax.imshow(degreeHD, norm=normalizer, cmap='plasma', extent=extent, aspect='equal', interpolation='bicubic', origin='lower') plt.colorbar(backPlot, ax=ax).set_label( "Linear polarization degree (%)" + " @ " + wavelatex, fontsize='large') # if not in interactive mode, save the figure; otherwise leave it open if not ut.interactive(interactive): saveFilePath = ut.savePath( filepath, ".pdf", outDirPath=outDirPath, outFileName="{}_{}_poldegmap.pdf".format( insname, wavename)) plt.savefig(saveFilePath, bbox_inches='tight', pad_inches=0.25) plt.close() logging.info("Created {}".format(saveFilePath)) # ----------------------------------------------------------------- # plot the y-axis averaged linear polarization degree if plotDegAvg: # construct the plot fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize) degreeHD = np.sqrt( np.average(Q, axis=0)**2 + np.average(U, axis=0)**2) degreeHD /= np.average(I, axis=0) ax.plot(xgrid.value, degreeHD * 100) ax.set_xlim(xmin, xmax) ax.set_ylim(0, degreeScale) ax.set_title("{} {}".format(insname, wavelatex), fontsize='large') ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large') ax.set_ylabel('Average linear polarization degree (%)', fontsize='large') # if not in interactive mode, save the figure; otherwise leave it open if not ut.interactive(interactive): saveFilePath = ut.savePath( filepathI, ".pdf", outDirPath=outDirPath, outFileName="{}_{}_poldegavg.pdf".format( insname, wavename)) plt.savefig(saveFilePath, bbox_inches='tight', pad_inches=0.25) plt.close() logging.info("Created {}".format(saveFilePath)) # ----------------------------------------------------------------- # plot a circular polarization map if plotCirMap: fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize) # configure the axes ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large') ax.set_ylabel("y" + sm.latexForUnit(ygrid), fontsize='large') # determine intensity range for the background image, ignoring pixels with outrageously high flux Ib = I.copy() highmask = Ib > 1e6 * np.nanmedian(np.unique(Ib)) vmax = np.nanmax(Ib[~highmask]) Ib[highmask] = vmax vmin = vmax / 10**decades # plot the background image and the corresponding color bar normalizer = matplotlib.colors.LogNorm(vmin, vmax) cmap = plt.get_cmap('PuRd') cmap.set_under('w') backPlot = ax.imshow(Ib, norm=normalizer, cmap=cmap, extent=extent, aspect='equal', interpolation='bicubic', origin='lower') cbarlabel = sm.latexForSpectralFlux(Is) + sm.latexForUnit( Is) + " @ " + wavelatex plt.colorbar(backPlot, ax=ax).set_label(cbarlabel, fontsize='large') # compute the circular polarization degree degreeLD = binnedV.copy() degreeLD[binnedI > 0] /= binnedI[binnedI > 0] # determine the scaling and add legend if degreeScale is None: degreeScale = _roundUp(np.percentile(np.abs(degreeLD), 99)) lengthScale = 0.7 / max(len(posX), len(posY)) _circArrow(ax, 0.84 - lengthScale / 2, 0.01 + lengthScale / 2, lengthScale) key = r'$+{} \%$'.format(100 * degreeScale) ax.text(0.85, 0.01 + lengthScale / 2, key, transform=ax.transAxes, ha='left', va='center') # actual plotting for x in range(len(posX)): for y in range(len(posY)): if np.isfinite(degreeLD[y, x]) and abs( degreeLD[y, x]) > degreeScale / 50: _circArrow( ax, posX[x] / orLenX, posY[y] / orLenY, degreeLD[y, x] / degreeScale * lengthScale) # if not in interactive mode, save the figure; otherwise leave it open if not ut.interactive(interactive): saveFilePath = ut.savePath( filepath, ".pdf", outDirPath=outDirPath, outFileName="{}_{}_polcirmap.pdf".format( insname, wavename)) plt.savefig(saveFilePath, bbox_inches='tight', pad_inches=0.25) plt.close() logging.info("Created {}".format(saveFilePath))