python-microscopy 25.6.5__cp39-cp39-win_amd64.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.
- PYME/Acquire/ActionManager.py +450 -0
- PYME/Acquire/ExecTools.py +235 -0
- PYME/Acquire/Hardware/AAOptoelectronics/MDS.py +175 -0
- PYME/Acquire/Hardware/AAOptoelectronics/__init__.py +0 -0
- PYME/Acquire/Hardware/ARCoptix/__init__.py +0 -0
- PYME/Acquire/Hardware/ARCoptix/lcdriver.py +149 -0
- PYME/Acquire/Hardware/ARCoptix/lcserver32.py +123 -0
- PYME/Acquire/Hardware/AndorIXon/AndorCam.py +1095 -0
- PYME/Acquire/Hardware/AndorIXon/AndorControlFrame.py +312 -0
- PYME/Acquire/Hardware/AndorIXon/AndorIXon.py +737 -0
- PYME/Acquire/Hardware/AndorIXon/__init__.py +27 -0
- PYME/Acquire/Hardware/AndorNeo/AndorNeo.py +644 -0
- PYME/Acquire/Hardware/AndorNeo/AndorNeoControlFrame.py +161 -0
- PYME/Acquire/Hardware/AndorNeo/AndorZyla.py +761 -0
- PYME/Acquire/Hardware/AndorNeo/SDK3.py +307 -0
- PYME/Acquire/Hardware/AndorNeo/SDK3Cam.py +171 -0
- PYME/Acquire/Hardware/AndorNeo/ZylaControlPanel.py +105 -0
- PYME/Acquire/Hardware/AndorNeo/__init__.py +22 -0
- PYME/Acquire/Hardware/AndorNeo/parseAtdebug.py +46 -0
- PYME/Acquire/Hardware/AndorNeo/plotTimings.py +73 -0
- PYME/Acquire/Hardware/AndorNeo/testNeo.py +62 -0
- PYME/Acquire/Hardware/Camera.py +1199 -0
- PYME/Acquire/Hardware/CameraSkeleton.py +642 -0
- PYME/Acquire/Hardware/Coherent/OBIS.py +147 -0
- PYME/Acquire/Hardware/Coherent/Sapphire.py +162 -0
- PYME/Acquire/Hardware/Coherent/__init__.py +0 -0
- PYME/Acquire/Hardware/DMDGui.py +198 -0
- PYME/Acquire/Hardware/DigiData/DigiData.py +95 -0
- PYME/Acquire/Hardware/DigiData/DigiDataClient.py +30 -0
- PYME/Acquire/Hardware/DigiData/RemoteDigiData.py +51 -0
- PYME/Acquire/Hardware/DigiData/__init__.py +24 -0
- PYME/Acquire/Hardware/DigiData/axDD132x.py +348 -0
- PYME/Acquire/Hardware/EMCCDTheory.py +41 -0
- PYME/Acquire/Hardware/ExciterWheel.py +167 -0
- PYME/Acquire/Hardware/FilterWheel.py +184 -0
- PYME/Acquire/Hardware/FocCorr.py +106 -0
- PYME/Acquire/Hardware/FocCorrR.py +196 -0
- PYME/Acquire/Hardware/FrFilter.py +51 -0
- PYME/Acquire/Hardware/GCS/GCS_DLL.py +370 -0
- PYME/Acquire/Hardware/GCS/__init__.py +0 -0
- PYME/Acquire/Hardware/GCS/gcs.py +210 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/HamamatsuDCAM.py +662 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/HamamatsuORCA.py +602 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/Hamamatsu_control_panel.py +54 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/__init__.py +23 -0
- PYME/Acquire/Hardware/LaserControlFrame.py +160 -0
- PYME/Acquire/Hardware/MPBCommunications/MPBCW.py +132 -0
- PYME/Acquire/Hardware/MPBCommunications/__init__.py +0 -0
- PYME/Acquire/Hardware/Mercury/Mercury.py +269 -0
- PYME/Acquire/Hardware/Mercury/PI_Mercury_GCS_DLL.py +857 -0
- PYME/Acquire/Hardware/Mercury/__init__.py +22 -0
- PYME/Acquire/Hardware/Mercury/mercuryStepper.py +203 -0
- PYME/Acquire/Hardware/Mercury/mercuryStepperGCS.py +281 -0
- PYME/Acquire/Hardware/NikonTE2000.py +68 -0
- PYME/Acquire/Hardware/NikonTi.py +214 -0
- PYME/Acquire/Hardware/NikonTiGUI.py +81 -0
- PYME/Acquire/Hardware/OrielCornerstone.py +125 -0
- PYME/Acquire/Hardware/PM100USB.py +51 -0
- PYME/Acquire/Hardware/Piezos/__init__.py +22 -0
- PYME/Acquire/Hardware/Piezos/base_piezo.py +108 -0
- PYME/Acquire/Hardware/Piezos/offsetPiezo.py +162 -0
- PYME/Acquire/Hardware/Piezos/offsetPiezoREST.py +308 -0
- PYME/Acquire/Hardware/Piezos/piezo_c867.py +486 -0
- PYME/Acquire/Hardware/Piezos/piezo_e255.py +68 -0
- PYME/Acquire/Hardware/Piezos/piezo_e662.py +61 -0
- PYME/Acquire/Hardware/Piezos/piezo_e709.py +434 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816.py +452 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816_corr.py +70 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816_dll.py +518 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816b.py +70 -0
- PYME/Acquire/Hardware/Piezos/piezo_pipython_gcs.py +560 -0
- PYME/Acquire/Hardware/Piezos/piezo_test.py +55 -0
- PYME/Acquire/Hardware/Simulator/EmpiricalHist.py +273 -0
- PYME/Acquire/Hardware/Simulator/__init__.py +22 -0
- PYME/Acquire/Hardware/Simulator/dSimControl.py +961 -0
- PYME/Acquire/Hardware/Simulator/fakeCam.py +651 -0
- PYME/Acquire/Hardware/Simulator/fakePiezo.py +95 -0
- PYME/Acquire/Hardware/Simulator/fluor.py +336 -0
- PYME/Acquire/Hardware/Simulator/illuminate.cp39-win_amd64.dll.a +0 -0
- PYME/Acquire/Hardware/Simulator/illuminate.cp39-win_amd64.pyd +0 -0
- PYME/Acquire/Hardware/Simulator/illuminate.pyx +93 -0
- PYME/Acquire/Hardware/Simulator/lasersliders.py +90 -0
- PYME/Acquire/Hardware/Simulator/rend_im.py +446 -0
- PYME/Acquire/Hardware/Simulator/setup.py +69 -0
- PYME/Acquire/Hardware/Simulator/simcontrol.py +348 -0
- PYME/Acquire/Hardware/Simulator/simui_wx.py +794 -0
- PYME/Acquire/Hardware/Tango/__init__.py +0 -0
- PYME/Acquire/Hardware/Tango/marzhauser_tango.py +583 -0
- PYME/Acquire/Hardware/TiLightCrafter.py +388 -0
- PYME/Acquire/Hardware/__init__.py +22 -0
- PYME/Acquire/Hardware/aotf.py +271 -0
- PYME/Acquire/Hardware/arclampshutterpanel.py +71 -0
- PYME/Acquire/Hardware/cameraSoftwareBuffer.py +215 -0
- PYME/Acquire/Hardware/camera_noise.py +270 -0
- PYME/Acquire/Hardware/ccdAdjPanel.py +188 -0
- PYME/Acquire/Hardware/ccdCalibrator.py +161 -0
- PYME/Acquire/Hardware/cobaltLaser.py +108 -0
- PYME/Acquire/Hardware/cobaltLaser561.py +94 -0
- PYME/Acquire/Hardware/comports.py +26 -0
- PYME/Acquire/Hardware/driftTrackGUI.py +488 -0
- PYME/Acquire/Hardware/driftTracking.py +595 -0
- PYME/Acquire/Hardware/fakeShutters.py +52 -0
- PYME/Acquire/Hardware/focusKeys.py +204 -0
- PYME/Acquire/Hardware/focus_locks/__init__.py +0 -0
- PYME/Acquire/Hardware/focus_locks/reflection_focus_lock.py +684 -0
- PYME/Acquire/Hardware/frZStage.py +117 -0
- PYME/Acquire/Hardware/fw102.py +38 -0
- PYME/Acquire/Hardware/ids_peak_cam.py +561 -0
- PYME/Acquire/Hardware/ioslave.py +305 -0
- PYME/Acquire/Hardware/lasers.py +292 -0
- PYME/Acquire/Hardware/matchboxLaser.py +58 -0
- PYME/Acquire/Hardware/microscope_adapter.py +104 -0
- PYME/Acquire/Hardware/mpd_picosecond_delayer.py +395 -0
- PYME/Acquire/Hardware/multiview.py +287 -0
- PYME/Acquire/Hardware/olympusix81.py +59 -0
- PYME/Acquire/Hardware/pco/__init__.py +0 -0
- PYME/Acquire/Hardware/pco/pco_cam.py +348 -0
- PYME/Acquire/Hardware/pco/pco_edge_42_lt.py +34 -0
- PYME/Acquire/Hardware/pco/pco_sdk.py +1645 -0
- PYME/Acquire/Hardware/pco/pco_sdk_cam.py +518 -0
- PYME/Acquire/Hardware/pco/pco_sdk_cam_control_panel.py +54 -0
- PYME/Acquire/Hardware/phoxxLaser.py +199 -0
- PYME/Acquire/Hardware/phoxxLaserOLD.py +242 -0
- PYME/Acquire/Hardware/priorLumen.py +102 -0
- PYME/Acquire/Hardware/priorarclampshutter.py +52 -0
- PYME/Acquire/Hardware/setup.py +50 -0
- PYME/Acquire/Hardware/spacenav.py +148 -0
- PYME/Acquire/Hardware/splitter.py +394 -0
- PYME/Acquire/Hardware/thorlabsPiezo.py +98 -0
- PYME/Acquire/Hardware/thorlabs_cam.py +136 -0
- PYME/Acquire/Hardware/thorlabs_mff_flipper.py +400 -0
- PYME/Acquire/Hardware/toptica_ibeam.py +51 -0
- PYME/Acquire/Hardware/uc480/AndorControlFrame.py +308 -0
- PYME/Acquire/Hardware/uc480/__init__.py +27 -0
- PYME/Acquire/Hardware/uc480/uCam480.py +695 -0
- PYME/Acquire/Hardware/uc480/uc480.py +189 -0
- PYME/Acquire/Hardware/uc480/uc480Deprecated.py +87 -0
- PYME/Acquire/Hardware/uc480/uc480_h.py +767 -0
- PYME/Acquire/Hardware/uc480/uc480_h_gen.py +62 -0
- PYME/Acquire/Hardware/uc480/ucCamControlFrame.py +83 -0
- PYME/Acquire/Hardware/ueye.py +702 -0
- PYME/Acquire/PYMEAcquire.py +186 -0
- PYME/Acquire/Protocols/DMDMFM.py +74 -0
- PYME/Acquire/Protocols/__init__.py +22 -0
- PYME/Acquire/Protocols/darkCalibrate.py +56 -0
- PYME/Acquire/Protocols/dual671_470.py +45 -0
- PYME/Acquire/Protocols/dual671_488.py +46 -0
- PYME/Acquire/Protocols/gainCal.py +53 -0
- PYME/Acquire/Protocols/htsms-cal-psf.py +32 -0
- PYME/Acquire/Protocols/htsms-cal-registration.py +52 -0
- PYME/Acquire/Protocols/htsms-flow.py +29 -0
- PYME/Acquire/Protocols/htsms-staggered.py +31 -0
- PYME/Acquire/Protocols/htsms-tile.py +68 -0
- PYME/Acquire/Protocols/htsms-two-color.py +29 -0
- PYME/Acquire/Protocols/htsms-widefield.py +32 -0
- PYME/Acquire/Protocols/paint671.py +61 -0
- PYME/Acquire/Protocols/photoconversion.py +52 -0
- PYME/Acquire/Protocols/photoconversion2.py +55 -0
- PYME/Acquire/Protocols/prebleach488.py +70 -0
- PYME/Acquire/Protocols/prebleach490.py +69 -0
- PYME/Acquire/Protocols/prebleach561.py +72 -0
- PYME/Acquire/Protocols/prebleach561NeomEos2.py +70 -0
- PYME/Acquire/Protocols/prebleach561NeomEos2ND1.py +70 -0
- PYME/Acquire/Protocols/prebleach642.py +70 -0
- PYME/Acquire/Protocols/prebleach671.py +69 -0
- PYME/Acquire/Protocols/prebleach671ND1.py +58 -0
- PYME/Acquire/Protocols/prebleach671ND2.py +57 -0
- PYME/Acquire/Protocols/prebleach671Neo.py +69 -0
- PYME/Acquire/Protocols/recover671.py +62 -0
- PYME/Acquire/Protocols/sequence642_488.py +85 -0
- PYME/Acquire/Protocols/sequence642_561.py +87 -0
- PYME/Acquire/Protocols/shiftfield.py +198 -0
- PYME/Acquire/Protocols/simul488.py +59 -0
- PYME/Acquire/Protocols/simul642.py +63 -0
- PYME/Acquire/Protocols/simul642HTSMS.py +63 -0
- PYME/Acquire/Protocols/simulPA.py +37 -0
- PYME/Acquire/Protocols/simulSIM.py +76 -0
- PYME/Acquire/Protocols/simulStep.py +62 -0
- PYME/Acquire/Protocols/spdscan.py +178 -0
- PYME/Acquire/Protocols/standard470.py +45 -0
- PYME/Acquire/Protocols/standard488.py +47 -0
- PYME/Acquire/Protocols/standard671.py +48 -0
- PYME/Acquire/Protocols/standard671_no532.py +47 -0
- PYME/Acquire/Protocols/standardArclamp.py +46 -0
- PYME/Acquire/Protocols/strip_tile.py +204 -0
- PYME/Acquire/Protocols/tile.py +204 -0
- PYME/Acquire/Protocols/tile3D.py +192 -0
- PYME/Acquire/Protocols/tile671.py +190 -0
- PYME/Acquire/Protocols/tile_triggered.py +189 -0
- PYME/Acquire/Scripts/init.py +232 -0
- PYME/Acquire/Scripts/init_N1_Ti_Exeter_ZIx.py +374 -0
- PYME/Acquire/Scripts/init_N2_Ti_Zyla_lasers.py +290 -0
- PYME/Acquire/Scripts/init_Neo.py +266 -0
- PYME/Acquire/Scripts/init_NeoSim.py +159 -0
- PYME/Acquire/Scripts/init_TIRF.py +232 -0
- PYME/Acquire/Scripts/init_TIRF_Neo.py +242 -0
- PYME/Acquire/Scripts/init_TIRF_NeoO.py +241 -0
- PYME/Acquire/Scripts/init_TIRF_onecam.py +233 -0
- PYME/Acquire/Scripts/init_Ti.py +257 -0
- PYME/Acquire/Scripts/init_UOA_n.py +256 -0
- PYME/Acquire/Scripts/init_UOA_n2.py +246 -0
- PYME/Acquire/Scripts/init_Y1.py +249 -0
- PYME/Acquire/Scripts/init_Zyla.py +260 -0
- PYME/Acquire/Scripts/init_drift_tracking.py +80 -0
- PYME/Acquire/Scripts/init_emccd_basic.py +277 -0
- PYME/Acquire/Scripts/init_htsms.py +277 -0
- PYME/Acquire/Scripts/init_htsms_focus_lock.py +132 -0
- PYME/Acquire/Scripts/init_micrpi.py +45 -0
- PYME/Acquire/Scripts/init_orca.py +70 -0
- PYME/Acquire/Scripts/init_pco.py +49 -0
- PYME/Acquire/Scripts/init_rev.py +74 -0
- PYME/Acquire/Scripts/init_sim100.py +169 -0
- PYME/Acquire/Scripts/init_sim103.py +169 -0
- PYME/Acquire/Scripts/init_sim2.py +211 -0
- PYME/Acquire/Scripts/init_sim50.py +169 -0
- PYME/Acquire/Scripts/init_sim75.py +169 -0
- PYME/Acquire/Scripts/init_sim_drift_tracking.py +166 -0
- PYME/Acquire/Scripts/init_sim_htsms.py +224 -0
- PYME/Acquire/Scripts/init_sim_htsms_n.py +233 -0
- PYME/Acquire/Scripts/init_sim_main.py +229 -0
- PYME/Acquire/Scripts/init_sim_min.py +197 -0
- PYME/Acquire/Scripts/init_sim_rem.py +176 -0
- PYME/Acquire/Scripts/init_smi1.py +110 -0
- PYME/Acquire/Scripts/init_spectro.py +78 -0
- PYME/Acquire/Scripts/init_spim.py +116 -0
- PYME/Acquire/Scripts/init_thorcam.py +62 -0
- PYME/Acquire/Scripts/init_twophoton.py +112 -0
- PYME/Acquire/Scripts/init_uc480.py +262 -0
- PYME/Acquire/Scripts/init_ueye.py +261 -0
- PYME/Acquire/SpoolController.py +958 -0
- PYME/Acquire/Utils/MultiPointScanner.py +80 -0
- PYME/Acquire/Utils/__init__.py +13 -0
- PYME/Acquire/Utils/failsafe.py +157 -0
- PYME/Acquire/Utils/fastTiler.py +153 -0
- PYME/Acquire/Utils/pointScanner.py +496 -0
- PYME/Acquire/Utils/sarcSpacing.py +86 -0
- PYME/Acquire/Utils/strip_tiler.py +93 -0
- PYME/Acquire/Utils/tiler.py +464 -0
- PYME/Acquire/Utils/vibrationAnalysis.py +65 -0
- PYME/Acquire/__init__.py +44 -0
- PYME/Acquire/acquire_client.py +90 -0
- PYME/Acquire/acquire_server.py +316 -0
- PYME/Acquire/acquirebase.py +139 -0
- PYME/Acquire/acquiremainframe.py +716 -0
- PYME/Acquire/acquirewx.py +61 -0
- PYME/Acquire/acquisition_base.py +148 -0
- PYME/Acquire/actions.py +209 -0
- PYME/Acquire/autofocus.py +54 -0
- PYME/Acquire/eventLog.py +95 -0
- PYME/Acquire/event_loop.py +119 -0
- PYME/Acquire/frameWrangler.py +530 -0
- PYME/Acquire/htsms/__init__.py +8 -0
- PYME/Acquire/htsms/rule_ui.py +970 -0
- PYME/Acquire/htsms/rule_ui_v2.py +573 -0
- PYME/Acquire/htsms/tweeter.py +222 -0
- PYME/Acquire/microscope.py +1050 -0
- PYME/Acquire/positionTracker.py +181 -0
- PYME/Acquire/protocol.py +299 -0
- PYME/Acquire/protocol_acquisition.py +404 -0
- PYME/Acquire/sampleInformation.py +591 -0
- PYME/Acquire/sampleInformationDjangoDirect.py +446 -0
- PYME/Acquire/setup.py +52 -0
- PYME/Acquire/stackSettings.py +295 -0
- PYME/Acquire/stage_leveling.py +285 -0
- PYME/Acquire/ui/AnalysisSettingsUI.py +184 -0
- PYME/Acquire/ui/HDFSpoolFrame.py +584 -0
- PYME/Acquire/ui/__init__.py +0 -0
- PYME/Acquire/ui/actionUI.py +892 -0
- PYME/Acquire/ui/focus_lock_gui.py +149 -0
- PYME/Acquire/ui/intensity_trace.py +143 -0
- PYME/Acquire/ui/intsliders.py +222 -0
- PYME/Acquire/ui/lasersliders.py +329 -0
- PYME/Acquire/ui/mpd_picosecond_delay_panel.py +69 -0
- PYME/Acquire/ui/multiview_select.py +77 -0
- PYME/Acquire/ui/positionUI.py +519 -0
- PYME/Acquire/ui/preflight.py +58 -0
- PYME/Acquire/ui/scanner_panel.py +41 -0
- PYME/Acquire/ui/selectCameraPanel.py +46 -0
- PYME/Acquire/ui/seqdialog.py +501 -0
- PYME/Acquire/ui/splashScreen.py +175 -0
- PYME/Acquire/ui/spool_panel.py +830 -0
- PYME/Acquire/ui/tile_panel.py +556 -0
- PYME/Acquire/ui/tilesettingsui.py +91 -0
- PYME/Acquire/ui/voxelSizeDialog.py +149 -0
- PYME/Acquire/webui/__init__.py +36 -0
- PYME/Acquire/webui/ipy.py +361 -0
- PYME/Acquire/webui/static/css/pyme-bootstrap.css +9908 -0
- PYME/Acquire/webui/static/css/pymeacquire.css +85 -0
- PYME/Acquire/webui/static/js/pymeacquire.js +375 -0
- PYME/Acquire/webui/static/js/pzf.js +95 -0
- PYME/Acquire/webui/templates/PYMEAcquire.html +232 -0
- PYME/Acquire/webui/templates/login.html +84 -0
- PYME/Acquire/webui/templates/xtermpage.html +106 -0
- PYME/Acquire/xyztc.py +418 -0
- PYME/Acquire/zScanner.py +287 -0
- PYME/Analysis/BleachProfile/__init__.py +26 -0
- PYME/Analysis/BleachProfile/deMod.py +91 -0
- PYME/Analysis/BleachProfile/fitRecover.py +90 -0
- PYME/Analysis/BleachProfile/intensProf.py +519 -0
- PYME/Analysis/BleachProfile/kinModels.py +507 -0
- PYME/Analysis/BleachProfile/rawIntensity.py +227 -0
- PYME/Analysis/BleachProfile/rollins.py +555 -0
- PYME/Analysis/Colocalisation/__init__.py +27 -0
- PYME/Analysis/Colocalisation/colocScaleDep.py +126 -0
- PYME/Analysis/Colocalisation/correlationCoeffs.py +198 -0
- PYME/Analysis/Colocalisation/distColoc.py +110 -0
- PYME/Analysis/Colocalisation/edtColoc.py +191 -0
- PYME/Analysis/MetaData.py +278 -0
- PYME/Analysis/PSFEst/__init__.py +27 -0
- PYME/Analysis/PSFEst/dec.py +283 -0
- PYME/Analysis/PSFEst/extractImages.py +316 -0
- PYME/Analysis/PSFEst/fit_psf_zernikes.py +194 -0
- PYME/Analysis/PSFEst/psfQuality.py +225 -0
- PYME/Analysis/PSFGen/__init__.py +40 -0
- PYME/Analysis/PSFGen/fourier.py +138 -0
- PYME/Analysis/PSFGen/fourierHNA.py +1108 -0
- PYME/Analysis/PSFGen/fourierHNA2.py +979 -0
- PYME/Analysis/PSFGen/genCRBcurves.py +185 -0
- PYME/Analysis/PSFGen/nijboer_zernike.py +79 -0
- PYME/Analysis/PSFGen/ps_app.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/PSFGen/ps_app.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/PSFGen/test_ps_app.py +24 -0
- PYME/Analysis/Tracking/__init__.py +3 -0
- PYME/Analysis/Tracking/msd.py +57 -0
- PYME/Analysis/Tracking/trackUtils.py +528 -0
- PYME/Analysis/Tracking/tracking.py +152 -0
- PYME/Analysis/__init__.py +30 -0
- PYME/Analysis/_fithelpers.py +161 -0
- PYME/Analysis/angleFilter.py +287 -0
- PYME/Analysis/annealThresh.py +157 -0
- PYME/Analysis/autoencoder_classifier.py +45 -0
- PYME/Analysis/binAvg.py +144 -0
- PYME/Analysis/composite.py +183 -0
- PYME/Analysis/cramerRao.py +259 -0
- PYME/Analysis/deTile.py +441 -0
- PYME/Analysis/density_filter.py +45 -0
- PYME/Analysis/distributed_pyramid.py +426 -0
- PYME/Analysis/fiducial_matching.py +220 -0
- PYME/Analysis/gen_sCMOS_maps.py +355 -0
- PYME/Analysis/graphing_filters.py +311 -0
- PYME/Analysis/implicitFilter.py +359 -0
- PYME/Analysis/keep_pairs.py +69 -0
- PYME/Analysis/optic_flow.py +108 -0
- PYME/Analysis/piecewise.py +36 -0
- PYME/Analysis/piecewiseMapping.py +304 -0
- PYME/Analysis/piezo_movement_correction.py +153 -0
- PYME/Analysis/points/DeClump/__init__.py +78 -0
- PYME/Analysis/points/DeClump/deClump.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/DeClump/deClump.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/DeClump/deClumpGUI.py +87 -0
- PYME/Analysis/points/DeClump/pyDeClump.py +284 -0
- PYME/Analysis/points/DeClump/testClump.py +40 -0
- PYME/Analysis/points/DistHist/__init__.py +25 -0
- PYME/Analysis/points/DistHist/distHist.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/DistHist/distHist.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/DistHist/distHistThreaded.py +71 -0
- PYME/Analysis/points/DistHist/setup.py +54 -0
- PYME/Analysis/points/EdgeDB/__init__.py +24 -0
- PYME/Analysis/points/EdgeDB/edgeDB.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/EdgeDB/edgeDB.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/EdgeDB/edgeTest.py +66 -0
- PYME/Analysis/points/EdgeDB/edges.py +102 -0
- PYME/Analysis/points/EdgeDB/segment.py +75 -0
- PYME/Analysis/points/EdgeDB/setup.py +54 -0
- PYME/Analysis/points/QuadTree/QTrend.py +232 -0
- PYME/Analysis/points/QuadTree/__init__.py +25 -0
- PYME/Analysis/points/QuadTree/pointQT.py +227 -0
- PYME/Analysis/points/QuadTree/pointQTF.py +254 -0
- PYME/Analysis/points/QuadTree/qtUtils.py +36 -0
- PYME/Analysis/points/QuadTree/rendQT.py +118 -0
- PYME/Analysis/points/SoftRend/__init__.py +24 -0
- PYME/Analysis/points/SoftRend/setup.py +61 -0
- PYME/Analysis/points/SoftRend/testTetrahedra.py +41 -0
- PYME/Analysis/points/SoftRend/triRend.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/SoftRend/triRend.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/__init__.py +3 -0
- PYME/Analysis/points/arcfit/__init__.py +1 -0
- PYME/Analysis/points/arcfit/arcmf.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/arcfit/arcmf.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/arcfit/setup.py +51 -0
- PYME/Analysis/points/astigmatism/__init__.py +0 -0
- PYME/Analysis/points/astigmatism/astigTools.py +338 -0
- PYME/Analysis/points/astigmatism/astiglookup.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/astigmatism/astiglookup.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/astigmatism/setup.py +54 -0
- PYME/Analysis/points/cluster_morphology.py +199 -0
- PYME/Analysis/points/coordinate_tools.py +278 -0
- PYME/Analysis/points/features/__init__.py +0 -0
- PYME/Analysis/points/features/metal.py +46 -0
- PYME/Analysis/points/features/shaders/vect_feat_3d.metal +49 -0
- PYME/Analysis/points/fiducials.py +142 -0
- PYME/Analysis/points/gen3DTriangs.py +796 -0
- PYME/Analysis/points/moments.py +143 -0
- PYME/Analysis/points/multiview.py +458 -0
- PYME/Analysis/points/objectMeasure.py +206 -0
- PYME/Analysis/points/qpaint.py +97 -0
- PYME/Analysis/points/rings.py +372 -0
- PYME/Analysis/points/ripleys.py +427 -0
- PYME/Analysis/points/segmentLL.py +134 -0
- PYME/Analysis/points/setup.py +56 -0
- PYME/Analysis/points/spherical_harmonics.py +909 -0
- PYME/Analysis/points/surfit.py +829 -0
- PYME/Analysis/points/tCluster.py +75 -0
- PYME/Analysis/points/traveling_salesperson/__init__.py +0 -0
- PYME/Analysis/points/traveling_salesperson/queue_opt.py +117 -0
- PYME/Analysis/points/traveling_salesperson/sectioned_two_opt.py +327 -0
- PYME/Analysis/points/traveling_salesperson/setup.py +40 -0
- PYME/Analysis/points/traveling_salesperson/sort.py +78 -0
- PYME/Analysis/points/traveling_salesperson/two_opt.py +189 -0
- PYME/Analysis/points/traveling_salesperson/two_opt_utils.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/traveling_salesperson/two_opt_utils.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/traveling_salesperson/two_opt_utils.pyx +41 -0
- PYME/Analysis/points/twoColour.py +572 -0
- PYME/Analysis/points/twoColourPlot.py +107 -0
- PYME/Analysis/processLogger.py +129 -0
- PYME/Analysis/profile_extraction.py +72 -0
- PYME/Analysis/setup.py +60 -0
- PYME/Analysis/splitting.py +372 -0
- PYME/Analysis/svmSegment.py +214 -0
- PYME/Analysis/thresholding.py +192 -0
- PYME/Analysis/tile_pyramid.py +938 -0
- PYME/Analysis/widefieldify.py +54 -0
- PYME/DSView/DisplayOptionsPanel.py +503 -0
- PYME/DSView/LUT/__init__.py +33 -0
- PYME/DSView/LUT/lut.cp39-win_amd64.dll.a +0 -0
- PYME/DSView/LUT/lut.cp39-win_amd64.pyd +0 -0
- PYME/DSView/LUT/setup.py +54 -0
- PYME/DSView/OpenSequenceDialog.py +83 -0
- PYME/DSView/OverlaysPanel.py +111 -0
- PYME/DSView/__init__.py +32 -0
- PYME/DSView/arrayViewPanel.py +1035 -0
- PYME/DSView/displayOptions.py +453 -0
- PYME/DSView/displaySettingsPanel.py +271 -0
- PYME/DSView/dsviewer.py +542 -0
- PYME/DSView/eventLogViewer.py +805 -0
- PYME/DSView/fitInfo.py +486 -0
- PYME/DSView/htmlServe.py +72 -0
- PYME/DSView/logparser.py +100 -0
- PYME/DSView/modules/LMAnalysis.py +1053 -0
- PYME/DSView/modules/LMDisplay.py +194 -0
- PYME/DSView/modules/__init__.py +162 -0
- PYME/DSView/modules/_base.py +39 -0
- PYME/DSView/modules/annotation.py +725 -0
- PYME/DSView/modules/arrayView.py +71 -0
- PYME/DSView/modules/blobFinding.py +455 -0
- PYME/DSView/modules/blobMeasure.py +1093 -0
- PYME/DSView/modules/camera_maps.py +38 -0
- PYME/DSView/modules/coloc.py +414 -0
- PYME/DSView/modules/composite.py +715 -0
- PYME/DSView/modules/convnet.py +24 -0
- PYME/DSView/modules/cropping.py +133 -0
- PYME/DSView/modules/dark_and_flatfield.py +31 -0
- PYME/DSView/modules/deconvolution.py +499 -0
- PYME/DSView/modules/eventView.py +80 -0
- PYME/DSView/modules/extractBlobs.py +46 -0
- PYME/DSView/modules/fastGraphPanel.py +85 -0
- PYME/DSView/modules/filtering.py +335 -0
- PYME/DSView/modules/flowView.py +268 -0
- PYME/DSView/modules/fourierRingCorrelation.py +172 -0
- PYME/DSView/modules/gl_image_view.py +12 -0
- PYME/DSView/modules/graphViewPanel.py +272 -0
- PYME/DSView/modules/measure/__init__.py +1 -0
- PYME/DSView/modules/measure/filterPan.py +239 -0
- PYME/DSView/modules/metadataView.py +28 -0
- PYME/DSView/modules/multithreshold.py +98 -0
- PYME/DSView/modules/neuroglancer.py +129 -0
- PYME/DSView/modules/object_tagging.py +157 -0
- PYME/DSView/modules/particleTracking.py +463 -0
- PYME/DSView/modules/playback.py +256 -0
- PYME/DSView/modules/prebleachExtraction.py +75 -0
- PYME/DSView/modules/profileFitting.py +137 -0
- PYME/DSView/modules/profilePlotting.py +408 -0
- PYME/DSView/modules/projections.py +48 -0
- PYME/DSView/modules/psfExtraction.py +570 -0
- PYME/DSView/modules/psfGeneration.py +138 -0
- PYME/DSView/modules/psfTools.py +661 -0
- PYME/DSView/modules/pupilTools.py +204 -0
- PYME/DSView/modules/pupilView.py +139 -0
- PYME/DSView/modules/recipes.py +266 -0
- PYME/DSView/modules/redimension.py +75 -0
- PYME/DSView/modules/segmentation.py +192 -0
- PYME/DSView/modules/sheetView.py +24 -0
- PYME/DSView/modules/shell.py +39 -0
- PYME/DSView/modules/shiftfield_estimation.py +433 -0
- PYME/DSView/modules/sparkprepare.py +94 -0
- PYME/DSView/modules/splitter.py +148 -0
- PYME/DSView/modules/synchronise.py +64 -0
- PYME/DSView/modules/templates/astigCal.html +42 -0
- PYME/DSView/modules/templates/measureView.html +69 -0
- PYME/DSView/modules/templates/measureView2.html +48 -0
- PYME/DSView/modules/templates/measureView3.html +98 -0
- PYME/DSView/modules/templates/trackView.html +139 -0
- PYME/DSView/modules/templates/tracksOverview.html +21 -0
- PYME/DSView/modules/tiling.py +147 -0
- PYME/DSView/modules/unmixing.py +148 -0
- PYME/DSView/modules/vis3D.py +320 -0
- PYME/DSView/modules/visgui.py +163 -0
- PYME/DSView/modules/webview.py +14 -0
- PYME/DSView/overlays.py +295 -0
- PYME/DSView/scrolledImagePanel.py +188 -0
- PYME/DSView/setup.py +53 -0
- PYME/DSView/splashScreen.py +158 -0
- PYME/DSView/voxSizeDialog.py +84 -0
- PYME/Deconv/__init__.py +35 -0
- PYME/Deconv/beadGen.py +55 -0
- PYME/Deconv/dec.py +659 -0
- PYME/Deconv/decTask.py +65 -0
- PYME/Deconv/decThread.py +113 -0
- PYME/Deconv/dec_test.py +47 -0
- PYME/Deconv/deconvDialogs.py +417 -0
- PYME/Deconv/fftwWisdom.py +62 -0
- PYME/Deconv/richardsonLucy.py +141 -0
- PYME/Deconv/richardsonLucyMV.py +151 -0
- PYME/Deconv/richardsonLucyMVM.py +218 -0
- PYME/Deconv/test_block_dec.py +46 -0
- PYME/Deconv/tq_block_dec.py +331 -0
- PYME/Deconv/wiener.py +163 -0
- PYME/IO/DataSources/AlignDataSource.py +91 -0
- PYME/IO/DataSources/ArrayDataSource.py +260 -0
- PYME/IO/DataSources/BGSDataSource.py +353 -0
- PYME/IO/DataSources/BaseDataSource.py +457 -0
- PYME/IO/DataSources/BioformatsDataSource.py +178 -0
- PYME/IO/DataSources/BufferedDataSource.py +99 -0
- PYME/IO/DataSources/ClusterPZFDataSource.py +124 -0
- PYME/IO/DataSources/ConcatenatedDataSource.py +56 -0
- PYME/IO/DataSources/CropDataSource.py +243 -0
- PYME/IO/DataSources/DcimgDataSource.py +54 -0
- PYME/IO/DataSources/ElectronsPerSecondDataSource.py +61 -0
- PYME/IO/DataSources/FlatFieldDataSource.py +77 -0
- PYME/IO/DataSources/HDFDataSource.py +160 -0
- PYME/IO/DataSources/HTTPDataSource.py +80 -0
- PYME/IO/DataSources/ImageSeriesDataSource.py +123 -0
- PYME/IO/DataSources/MultiviewDataSource.py +99 -0
- PYME/IO/DataSources/OBFDataSource.py +67 -0
- PYME/IO/DataSources/RandomDataSource.py +23 -0
- PYME/IO/DataSources/SupertileDatasource.py +148 -0
- PYME/IO/DataSources/TQDataSource.py +41 -0
- PYME/IO/DataSources/TiffDataSource.py +193 -0
- PYME/IO/DataSources/TileDataSource.py +91 -0
- PYME/IO/DataSources/UnsplitDataSource.py +92 -0
- PYME/IO/DataSources/__init__.py +59 -0
- PYME/IO/FileUtils/IJLut.py +43 -0
- PYME/IO/FileUtils/RawToHdf5.py +105 -0
- PYME/IO/FileUtils/SPAD.py +54 -0
- PYME/IO/FileUtils/TiffSeqToHdf5.py +87 -0
- PYME/IO/FileUtils/__init__.py +24 -0
- PYME/IO/FileUtils/compHdf.py +39 -0
- PYME/IO/FileUtils/fileID.py +246 -0
- PYME/IO/FileUtils/fnlGen.py +40 -0
- PYME/IO/FileUtils/freeSpace.py +59 -0
- PYME/IO/FileUtils/h5-thumbnailer.py +85 -0
- PYME/IO/FileUtils/h5ExFrames.py +98 -0
- PYME/IO/FileUtils/h5ToTiffSeries.py +50 -0
- PYME/IO/FileUtils/h5r-thumbnailer.py +118 -0
- PYME/IO/FileUtils/h5rFormatDef.py +99 -0
- PYME/IO/FileUtils/h5rToTxt.py +77 -0
- PYME/IO/FileUtils/nameUtils.py +300 -0
- PYME/IO/FileUtils/obf_support.py +538 -0
- PYME/IO/FileUtils/ply.py +147 -0
- PYME/IO/FileUtils/readSpeckle.py +78 -0
- PYME/IO/FileUtils/readTiff.py +64 -0
- PYME/IO/FileUtils/saveTiffStack.py +137 -0
- PYME/IO/FileUtils/setup.py +36 -0
- PYME/IO/FileUtils/sf-thumbnailer.py +76 -0
- PYME/IO/FileUtils/stl.py +44 -0
- PYME/IO/FileUtils/thumbnailDatabase.py +104 -0
- PYME/IO/HTTPSpooler_v2.py +141 -0
- PYME/IO/MetaDataHandler.py +1072 -0
- PYME/IO/PZFFormat.py +375 -0
- PYME/IO/Spooler.py +279 -0
- PYME/IO/__init__.py +12 -0
- PYME/IO/acquisition_backends.py +353 -0
- PYME/IO/buffer_helpers.cp39-win_amd64.dll.a +0 -0
- PYME/IO/buffer_helpers.cp39-win_amd64.pyd +0 -0
- PYME/IO/buffer_helpers.pyx +190 -0
- PYME/IO/buffers.py +291 -0
- PYME/IO/clusterDuplication.py +204 -0
- PYME/IO/clusterExport.py +152 -0
- PYME/IO/clusterGlob.py +108 -0
- PYME/IO/clusterIO.py +1076 -0
- PYME/IO/clusterListing.py +232 -0
- PYME/IO/clusterResults.py +206 -0
- PYME/IO/clusterUpload.py +49 -0
- PYME/IO/cluster_directory.py +419 -0
- PYME/IO/cluster_streaming.py +216 -0
- PYME/IO/compatibility.py +12 -0
- PYME/IO/countdir/__init__.py +5 -0
- PYME/IO/countdir/setup.py +58 -0
- PYME/IO/csv_flavours.py +251 -0
- PYME/IO/dataExporter.py +470 -0
- PYME/IO/dataWrap.py +108 -0
- PYME/IO/dcimg.py +167 -0
- PYME/IO/events.py +161 -0
- PYME/IO/h5File.py +148 -0
- PYME/IO/h5rFile.py +323 -0
- PYME/IO/image.py +1447 -0
- PYME/IO/load_psf.py +26 -0
- PYME/IO/pyme_h5ls.py +58 -0
- PYME/IO/ragged.py +138 -0
- PYME/IO/rgb_image.py +94 -0
- PYME/IO/tabular.py +1338 -0
- PYME/IO/testClusterSpooling.py +199 -0
- PYME/IO/unifiedIO.py +301 -0
- PYME/LMVis/Extras/QPobjectSegment.py +220 -0
- PYME/LMVis/Extras/__init__.py +55 -0
- PYME/LMVis/Extras/animation.py +359 -0
- PYME/LMVis/Extras/annotation.py +78 -0
- PYME/LMVis/Extras/clusterAnalysis.py +460 -0
- PYME/LMVis/Extras/dockedPanel.py +69 -0
- PYME/LMVis/Extras/extra_layers.py +373 -0
- PYME/LMVis/Extras/fiducial.py +230 -0
- PYME/LMVis/Extras/flagZMotionArtifacts.py +60 -0
- PYME/LMVis/Extras/fusionUtils.py +9 -0
- PYME/LMVis/Extras/multiColourAnalysis.py +103 -0
- PYME/LMVis/Extras/multiviewMapping.py +489 -0
- PYME/LMVis/Extras/objectMeasurements.py +246 -0
- PYME/LMVis/Extras/particleTracking.py +272 -0
- PYME/LMVis/Extras/perFrameVariable.py +77 -0
- PYME/LMVis/Extras/photophysics.py +72 -0
- PYME/LMVis/Extras/pointSetGeneration.py +232 -0
- PYME/LMVis/Extras/pointwiseColoc.py +54 -0
- PYME/LMVis/Extras/saved_views.py +125 -0
- PYME/LMVis/Extras/scaleBox.py +115 -0
- PYME/LMVis/Extras/sequentialSpecies.py +125 -0
- PYME/LMVis/Extras/setBGImage.py +74 -0
- PYME/LMVis/Extras/shiftmapGenerator.py +138 -0
- PYME/LMVis/Extras/showShortcuts.py +69 -0
- PYME/LMVis/Extras/snapshot.py +135 -0
- PYME/LMVis/Extras/spherical_harmonics.py +98 -0
- PYME/LMVis/Extras/subsets.py +15 -0
- PYME/LMVis/Extras/surface_fitting.py +52 -0
- PYME/LMVis/Extras/temperaturePlot.py +53 -0
- PYME/LMVis/Extras/vibration.py +112 -0
- PYME/LMVis/VisGUI.py +506 -0
- PYME/LMVis/__init__.py +39 -0
- PYME/LMVis/colourFilterGUI.py +77 -0
- PYME/LMVis/colourPanel.py +491 -0
- PYME/LMVis/displayPane.py +462 -0
- PYME/LMVis/dyeRatios.py +116 -0
- PYME/LMVis/genImageDialog.py +298 -0
- PYME/LMVis/gl_offScreenHandler.py +134 -0
- PYME/LMVis/gl_render.py +1302 -0
- PYME/LMVis/gl_render3D.py +1453 -0
- PYME/LMVis/gl_render3D_shaders.py +1213 -0
- PYME/LMVis/glcanvas_core.py +1157 -0
- PYME/LMVis/imageView.py +752 -0
- PYME/LMVis/imageView2.py +372 -0
- PYME/LMVis/importTextDialog.py +342 -0
- PYME/LMVis/layer_panel.py +231 -0
- PYME/LMVis/layers/AxesOverlayLayer.py +126 -0
- PYME/LMVis/layers/LUTOverlayLayer.py +346 -0
- PYME/LMVis/layers/OverlayLayer.py +46 -0
- PYME/LMVis/layers/ScaleBarOverlayLayer.py +97 -0
- PYME/LMVis/layers/ScaleBoxOverlayLayer.py +181 -0
- PYME/LMVis/layers/SelectionOverlayLayer.py +124 -0
- PYME/LMVis/layers/__init__.py +53 -0
- PYME/LMVis/layers/base.py +319 -0
- PYME/LMVis/layers/image_layer.py +363 -0
- PYME/LMVis/layers/labels.py +186 -0
- PYME/LMVis/layers/layer_defaults.py +97 -0
- PYME/LMVis/layers/mesh.py +378 -0
- PYME/LMVis/layers/octree.py +194 -0
- PYME/LMVis/layers/pointcloud.py +372 -0
- PYME/LMVis/layers/quiver.py +271 -0
- PYME/LMVis/layers/text.py +250 -0
- PYME/LMVis/layers/tracks.py +309 -0
- PYME/LMVis/mv_math.py +78 -0
- PYME/LMVis/objectDarkMeasure.py +120 -0
- PYME/LMVis/pipeline.py +1227 -0
- PYME/LMVis/pipelineView.py +115 -0
- PYME/LMVis/pointSettingsPanel.py +135 -0
- PYME/LMVis/progGraph.py +59 -0
- PYME/LMVis/quadTreeSettings.py +90 -0
- PYME/LMVis/rendGauss.py +158 -0
- PYME/LMVis/renderers.py +576 -0
- PYME/LMVis/sessionpaths.py +136 -0
- PYME/LMVis/setup.py +39 -0
- PYME/LMVis/setup_exe.py +71 -0
- PYME/LMVis/shader_programs/DefaultShaderProgram.py +149 -0
- PYME/LMVis/shader_programs/GLProgram.py +119 -0
- PYME/LMVis/shader_programs/GouraudShaderProgram.py +157 -0
- PYME/LMVis/shader_programs/PointSpriteShaderProgram.py +163 -0
- PYME/LMVis/shader_programs/ShaderProgramFactory.py +54 -0
- PYME/LMVis/shader_programs/TesselShaderProgram.py +47 -0
- PYME/LMVis/shader_programs/WireFrameShaderProgram.py +50 -0
- PYME/LMVis/shader_programs/__init__.py +20 -0
- PYME/LMVis/shader_programs/oit_compositor.py +23 -0
- PYME/LMVis/shader_programs/shader_loader.py +84 -0
- PYME/LMVis/shader_programs/shader_program.py +168 -0
- PYME/LMVis/shader_programs/shaders/bigflatpoints_fs.glsl +16 -0
- PYME/LMVis/shader_programs/shaders/bigpoints_gs.glsl +40 -0
- PYME/LMVis/shader_programs/shaders/bigpointsprites_fs.glsl +20 -0
- PYME/LMVis/shader_programs/shaders/bigspheres_fs.glsl +53 -0
- PYME/LMVis/shader_programs/shaders/compose_fs.glsl +41 -0
- PYME/LMVis/shader_programs/shaders/compose_vs.glsl +25 -0
- PYME/LMVis/shader_programs/shaders/default_fs.glsl +27 -0
- PYME/LMVis/shader_programs/shaders/default_vs.glsl +88 -0
- PYME/LMVis/shader_programs/shaders/flatpoints_fs.glsl +27 -0
- PYME/LMVis/shader_programs/shaders/gouraud_flatpoints_fs.glsl +30 -0
- PYME/LMVis/shader_programs/shaders/gouraud_fs.glsl +26 -0
- PYME/LMVis/shader_programs/shaders/gouraud_oit_fs.glsl +68 -0
- PYME/LMVis/shader_programs/shaders/gouraud_vs.glsl +135 -0
- PYME/LMVis/shader_programs/shaders/image_fs.glsl +60 -0
- PYME/LMVis/shader_programs/shaders/image_vs.glsl +79 -0
- PYME/LMVis/shader_programs/shaders/phong_fs.glsl +43 -0
- PYME/LMVis/shader_programs/shaders/phong_vs.glsl +45 -0
- PYME/LMVis/shader_programs/shaders/pointsprites_fs.glsl +33 -0
- PYME/LMVis/shader_programs/shaders/pointsprites_vs.glsl +77 -0
- PYME/LMVis/shader_programs/shaders/spheres_fs.glsl +102 -0
- PYME/LMVis/shader_programs/shaders/tessel_fs.glsl +23 -0
- PYME/LMVis/shader_programs/shaders/tessel_vs.glsl +37 -0
- PYME/LMVis/shader_programs/shaders/text_fs.glsl +42 -0
- PYME/LMVis/shader_programs/shaders/text_vs.glsl +84 -0
- PYME/LMVis/shader_programs/shaders/widelines_fs.glsl +19 -0
- PYME/LMVis/shader_programs/shaders/widelines_gs.glsl +56 -0
- PYME/LMVis/shader_programs/shaders/widelines_vs.glsl +51 -0
- PYME/LMVis/statusLog.py +57 -0
- PYME/LMVis/triBlobs.py +142 -0
- PYME/LMVis/view_clipping_pane.py +429 -0
- PYME/LMVis/views.py +275 -0
- PYME/LMVis/visCore.py +1005 -0
- PYME/LMVis/visHelpers.py +880 -0
- PYME/LMVis/visHelpersMin.py +230 -0
- PYME/LMVis/workspaceTree.py +238 -0
- PYME/__init__.py +22 -0
- PYME/cluster/HTTPDataServer.py +1063 -0
- PYME/cluster/HTTPRulePusher.py +382 -0
- PYME/cluster/HTTPTaskPusher.py +377 -0
- PYME/cluster/PYMERuleNodeServer.py +192 -0
- PYME/cluster/PYMERuleServer.py +109 -0
- PYME/cluster/__init__.py +0 -0
- PYME/cluster/clusterUI/accounts/__init__.py +0 -0
- PYME/cluster/clusterUI/accounts/admin.py +85 -0
- PYME/cluster/clusterUI/accounts/forms.py +8 -0
- PYME/cluster/clusterUI/accounts/migrations/0001_initial.py +31 -0
- PYME/cluster/clusterUI/accounts/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/accounts/models.py +44 -0
- PYME/cluster/clusterUI/accounts/templates/accounts/user_form.html +13 -0
- PYME/cluster/clusterUI/accounts/templates/registration/initial_complete.html +6 -0
- PYME/cluster/clusterUI/accounts/templates/registration/initial_confirm.html +13 -0
- PYME/cluster/clusterUI/accounts/templates/registration/initial_done.html +7 -0
- PYME/cluster/clusterUI/accounts/templates/registration/verification.html +2 -0
- PYME/cluster/clusterUI/accounts/templates/registration/verification_subject.txt +1 -0
- PYME/cluster/clusterUI/accounts/urls.py +19 -0
- PYME/cluster/clusterUI/accounts/views.py +36 -0
- PYME/cluster/clusterUI/clusterUI/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterUI/settings.py +147 -0
- PYME/cluster/clusterUI/clusterUI/urls.py +31 -0
- PYME/cluster/clusterUI/clusterUI/wsgi.py +16 -0
- PYME/cluster/clusterUI/clusterbrowser/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterbrowser/admin.py +3 -0
- PYME/cluster/clusterUI/clusterbrowser/apps.py +7 -0
- PYME/cluster/clusterUI/clusterbrowser/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterbrowser/models.py +5 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/base.html +129 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/dirlisting.html +231 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/form_new_folder.html +18 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/form_upload_file.html +17 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/lightlisting.html +25 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/registration/login.html +38 -0
- PYME/cluster/clusterUI/clusterbrowser/urls.py +10 -0
- PYME/cluster/clusterUI/clusterbrowser/views.py +148 -0
- PYME/cluster/clusterUI/clusterstatus/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterstatus/admin.py +3 -0
- PYME/cluster/clusterUI/clusterstatus/apps.py +7 -0
- PYME/cluster/clusterUI/clusterstatus/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterstatus/models.py +5 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/load_content.html +29 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/queue_info.html +28 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/queue_info_content.html +50 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/status_dash.html +29 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/status_dash_content.html +90 -0
- PYME/cluster/clusterUI/clusterstatus/urls.py +9 -0
- PYME/cluster/clusterUI/clusterstatus/views.py +69 -0
- PYME/cluster/clusterUI/clusterui_tornado.py +117 -0
- PYME/cluster/clusterUI/deploy.py +62 -0
- PYME/cluster/clusterUI/localization/__init__.py +0 -0
- PYME/cluster/clusterUI/localization/admin.py +3 -0
- PYME/cluster/clusterUI/localization/apps.py +7 -0
- PYME/cluster/clusterUI/localization/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/localization/models.py +5 -0
- PYME/cluster/clusterUI/localization/templates/localization/settings_form.html +67 -0
- PYME/cluster/clusterUI/localization/urls.py +10 -0
- PYME/cluster/clusterUI/localization/views.py +170 -0
- PYME/cluster/clusterUI/manage.py +17 -0
- PYME/cluster/clusterUI/recipes/__init__.py +0 -0
- PYME/cluster/clusterUI/recipes/admin.py +3 -0
- PYME/cluster/clusterUI/recipes/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/recipes/models.py +3 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/extra_inputs.html +35 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/form_recipe.html +82 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/input_list.html +7 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_editor.html +28 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_editor_common.html +46 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_editrun.html +93 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_standalone.html +100 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_template.html +109 -0
- PYME/cluster/clusterUI/recipes/urls.py +17 -0
- PYME/cluster/clusterUI/recipes/views.py +109 -0
- PYME/cluster/cluster_of_one.py +200 -0
- PYME/cluster/dataserver_wsgi.py +615 -0
- PYME/cluster/distribution.py +71 -0
- PYME/cluster/rulenodeserver.py +487 -0
- PYME/cluster/rules.py +979 -0
- PYME/cluster/ruleserver.py +1106 -0
- PYME/cluster/status.py +101 -0
- PYME/cluster/taskWorkerHTTP.py +522 -0
- PYME/cluster/webdav.py +1102 -0
- PYME/config.py +659 -0
- PYME/contrib/TextCtrlAutoComplete.py +582 -0
- PYME/contrib/__init__.py +7 -0
- PYME/contrib/aniso/__init__.py +2 -0
- PYME/contrib/aniso/aniso.py +268 -0
- PYME/contrib/auiFloatBook.py +94 -0
- PYME/contrib/congrid.py +127 -0
- PYME/contrib/dispatch/__init__.py +19 -0
- PYME/contrib/dispatch/dispatcher.py +289 -0
- PYME/contrib/gohlke/__init__.py +1 -0
- PYME/contrib/gohlke/_tifffile.cp39-win_amd64.dll.a +0 -0
- PYME/contrib/gohlke/_tifffile.cp39-win_amd64.pyd +0 -0
- PYME/contrib/gohlke/czifile.py +1289 -0
- PYME/contrib/gohlke/fcsfiles.py +514 -0
- PYME/contrib/gohlke/setup.py +54 -0
- PYME/contrib/gohlke/tifffile.py +3095 -0
- PYME/contrib/listctrlMixins.py +896 -0
- PYME/contrib/lzw.py +838 -0
- PYME/contrib/pad/__init__.py +13 -0
- PYME/contrib/pad/info_pad.py +18 -0
- PYME/contrib/pad/pad.py +670 -0
- PYME/contrib/setup.py +38 -0
- PYME/contrib/wxPlotPanel.py +137 -0
- PYME/experimental/SimReconstruction.py +49 -0
- PYME/experimental/__init__.pxd +0 -0
- PYME/experimental/__init__.py +4 -0
- PYME/experimental/_octree.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/_octree.cp39-win_amd64.pyd +0 -0
- PYME/experimental/_octree.pyx +477 -0
- PYME/experimental/_treap.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/_treap.cp39-win_amd64.pyd +0 -0
- PYME/experimental/_treap.pyx +267 -0
- PYME/experimental/_triangle_mesh.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/_triangle_mesh.cp39-win_amd64.pyd +0 -0
- PYME/experimental/_triangle_mesh.pxd +149 -0
- PYME/experimental/_triangle_mesh.pyx +2787 -0
- PYME/experimental/aqavg.py +40 -0
- PYME/experimental/dcimgFileChucker.py +375 -0
- PYME/experimental/dcimgSpoolShim.py +174 -0
- PYME/experimental/dcimg_chuck_test.py +78 -0
- PYME/experimental/dual_marching_cubes.py +801 -0
- PYME/experimental/dual_marching_cubes_v2.py +645 -0
- PYME/experimental/expectationMaximisation.py +298 -0
- PYME/experimental/func_octree.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/func_octree.cp39-win_amd64.pyd +0 -0
- PYME/experimental/func_octree.pyx +381 -0
- PYME/experimental/fusionRadial.py +520 -0
- PYME/experimental/fusionUtils.py +522 -0
- PYME/experimental/isosurface.py +147 -0
- PYME/experimental/labview_spooling_hacks.py +48 -0
- PYME/experimental/marching_cubes.py +811 -0
- PYME/experimental/marching_tetrahedra.py +343 -0
- PYME/experimental/modified_marching_cubes.py +1245 -0
- PYME/experimental/music.py +25 -0
- PYME/experimental/octree.py +188 -0
- PYME/experimental/pSpaceMap.py +34 -0
- PYME/experimental/pointFitConfoc.py +112 -0
- PYME/experimental/scanner_control.py +107 -0
- PYME/experimental/setup.py +109 -0
- PYME/experimental/spark.py +31 -0
- PYME/experimental/tcHist.py +71 -0
- PYME/experimental/treap.py +262 -0
- PYME/experimental/triangle_mesh.py +2287 -0
- PYME/experimental/triangle_mesh_utils.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/triangle_mesh_utils.cp39-win_amd64.pyd +0 -0
- PYME/experimental/triangle_utils.py +87 -0
- PYME/experimental/triangular_mesh.py +2255 -0
- PYME/experimental/zMotionArtifactUtils.py +55 -0
- PYME/localization/FitFactories/AstigGaussFitFR.py +175 -0
- PYME/localization/FitFactories/AstigGaussGPUFitFR.py +326 -0
- PYME/localization/FitFactories/BeadConvolvedAstigGaussFit.py +206 -0
- PYME/localization/FitFactories/ChecksumFR.py +82 -0
- PYME/localization/FitFactories/ChecksumGPUFR.py +213 -0
- PYME/localization/FitFactories/ChecksumMultifitSR.py +86 -0
- PYME/localization/FitFactories/ConfocCOIR.py +93 -0
- PYME/localization/FitFactories/Dumbell3DFitR.py +193 -0
- PYME/localization/FitFactories/DumbellFitR.py +163 -0
- PYME/localization/FitFactories/FFBase.py +403 -0
- PYME/localization/FitFactories/Gauss3DFitR.py +213 -0
- PYME/localization/FitFactories/GaussFitConstrR.py +177 -0
- PYME/localization/FitFactories/GaussMultifitLinesR.py +351 -0
- PYME/localization/FitFactories/GaussMultifitR.py +326 -0
- PYME/localization/FitFactories/GaussMultifitSR.py +373 -0
- PYME/localization/FitFactories/InterpFitR.py +236 -0
- PYME/localization/FitFactories/InterpFitWFR.py +111 -0
- PYME/localization/FitFactories/Interpolators/CSInterpolator.py +148 -0
- PYME/localization/FitFactories/Interpolators/CubeSInterpolatorP.py +121 -0
- PYME/localization/FitFactories/Interpolators/CubicSplineInterpolator.py +69 -0
- PYME/localization/FitFactories/Interpolators/LinearInterpolator.py +135 -0
- PYME/localization/FitFactories/Interpolators/LinearPInterpolatorP.py +95 -0
- PYME/localization/FitFactories/Interpolators/__init__.py +33 -0
- PYME/localization/FitFactories/Interpolators/baseInterpolator.py +202 -0
- PYME/localization/FitFactories/LatFitCOIR.py +82 -0
- PYME/localization/FitFactories/LatGaussFitFR.py +220 -0
- PYME/localization/FitFactories/LatObjFindFR.py +85 -0
- PYME/localization/FitFactories/LatPSFFitR.py +142 -0
- PYME/localization/FitFactories/MultiviewFitInterpNR.py +400 -0
- PYME/localization/FitFactories/PRInterpFitQR.py +192 -0
- PYME/localization/FitFactories/PRInterpFitR.py +223 -0
- PYME/localization/FitFactories/ROIExtractNR.py +93 -0
- PYME/localization/FitFactories/SpecGaussFitFR.py +174 -0
- PYME/localization/FitFactories/SplitterFitCOIR.py +142 -0
- PYME/localization/FitFactories/SplitterFitFNR.py +312 -0
- PYME/localization/FitFactories/SplitterFitFusionR.py +318 -0
- PYME/localization/FitFactories/SplitterFitInterpBNR.py +357 -0
- PYME/localization/FitFactories/SplitterFitInterpNR.py +343 -0
- PYME/localization/FitFactories/SplitterFitScavNR.py +352 -0
- PYME/localization/FitFactories/SplitterMultifitSR.py +410 -0
- PYME/localization/FitFactories/SplitterObjFindR.py +194 -0
- PYME/localization/FitFactories/SplitterROIExtractNR.py +91 -0
- PYME/localization/FitFactories/SplitterShiftEstFR.py +230 -0
- PYME/localization/FitFactories/__init__.py +103 -0
- PYME/localization/FitFactories/fitCommon.py +195 -0
- PYME/localization/FitFactories/zEstimators/__init__.py +29 -0
- PYME/localization/FitFactories/zEstimators/astigEstimator.py +173 -0
- PYME/localization/FitFactories/zEstimators/biplaneEstimator.py +209 -0
- PYME/localization/FitFactories/zEstimators/genericDualEstimator.py +232 -0
- PYME/localization/FitFactories/zEstimators/genericEstimator.py +224 -0
- PYME/localization/FitFactories/zEstimators/priEstimator.py +255 -0
- PYME/localization/MetaDataEdit.py +543 -0
- PYME/localization/Test/30_9_series_A_1.sf +0 -0
- PYME/localization/Test/PRI.md +45 -0
- PYME/localization/Test/PRI_qr.md +52 -0
- PYME/localization/Test/PRI_r.md +52 -0
- PYME/localization/Test/__init__.py +22 -0
- PYME/localization/Test/astig.md +50 -0
- PYME/localization/Test/astigCOI.md +50 -0
- PYME/localization/Test/astig_gauss.md +52 -0
- PYME/localization/Test/astig_pe.md +50 -0
- PYME/localization/Test/astig_splitter.md +66 -0
- PYME/localization/Test/astig_splitterCOI.md +67 -0
- PYME/localization/Test/astig_splitterQ.md +66 -0
- PYME/localization/Test/astig_theory.md +50 -0
- PYME/localization/Test/astig_theory.psf +0 -0
- PYME/localization/Test/astig_theory.tif +0 -0
- PYME/localization/Test/beads1.psf +0 -0
- PYME/localization/Test/beads1.tif +0 -0
- PYME/localization/Test/biplane_splitter.md +75 -0
- PYME/localization/Test/biplane_splitter2.md +103 -0
- PYME/localization/Test/biplane_splitter3.md +115 -0
- PYME/localization/Test/defoc.md +48 -0
- PYME/localization/Test/dumbell_3d.md +52 -0
- PYME/localization/Test/fitTestJig.py +172 -0
- PYME/localization/Test/fitTestJigSCMOS.py +487 -0
- PYME/localization/Test/fitTestJigWC.py +225 -0
- PYME/localization/Test/gauss.md +50 -0
- PYME/localization/Test/gaussCOI.md +50 -0
- PYME/localization/Test/pri_pe.md +50 -0
- PYME/localization/Test/pri_theory.md +51 -0
- PYME/localization/Test/pri_theory.psf +0 -0
- PYME/localization/Test/pri_theory.tif +0 -0
- PYME/localization/Test/psf_astig.psf +0 -0
- PYME/localization/Test/psf_astig.tif +0 -0
- PYME/localization/Test/psf_astig_bg_d.psf +0 -0
- PYME/localization/Test/psf_astig_bg_d.tif +0 -0
- PYME/localization/Test/psf_astig_pe.psf +0 -0
- PYME/localization/Test/psf_astig_pe.tif +0 -0
- PYME/localization/Test/psf_decon.psf +0 -0
- PYME/localization/Test/psf_decon.tif +0 -0
- PYME/localization/Test/psf_decon_pe.psf +0 -0
- PYME/localization/Test/psf_decon_pe.tif +0 -0
- PYME/localization/Test/psf_guess.psf +0 -0
- PYME/localization/Test/splitter.md +63 -0
- PYME/localization/Test/splitter_scav.md +63 -0
- PYME/localization/Test/test.psf +0 -0
- PYME/localization/Test/test.tif +0 -0
- PYME/localization/Test/test_decon.psf +0 -0
- PYME/localization/Test/test_decon.tif +0 -0
- PYME/localization/Test/test_pe.psf +0 -0
- PYME/localization/Test/test_pe.tif +0 -0
- PYME/localization/Test/wf_theory.psf +0 -0
- PYME/localization/Test/wf_theory2.psf +0 -0
- PYME/localization/__init__.py +17 -0
- PYME/localization/autopsf/__init__.py +1 -0
- PYME/localization/autopsf/autopsf.py +486 -0
- PYME/localization/autopsf/roifit.py +310 -0
- PYME/localization/cInterp/__init__.py +24 -0
- PYME/localization/cInterp/cInterp.cp39-win_amd64.dll.a +0 -0
- PYME/localization/cInterp/cInterp.cp39-win_amd64.pyd +0 -0
- PYME/localization/cInterp/setup.py +54 -0
- PYME/localization/cModels/__init__.py +24 -0
- PYME/localization/cModels/gauss_app.cp39-win_amd64.dll.a +0 -0
- PYME/localization/cModels/gauss_app.cp39-win_amd64.pyd +0 -0
- PYME/localization/ofind.py +444 -0
- PYME/localization/ofind3d.py +306 -0
- PYME/localization/ofind_nofilt.py +128 -0
- PYME/localization/ofind_pri.py +478 -0
- PYME/localization/ofind_split.py +399 -0
- PYME/localization/ofind_xcorr.py +351 -0
- PYME/localization/ofind_xcorr_fw.py +441 -0
- PYME/localization/remFitBuf.py +657 -0
- PYME/localization/setup.py +56 -0
- PYME/localization/splitting.py +240 -0
- PYME/localization/twist.py +60 -0
- PYME/misc/TiSettings.py +36 -0
- PYME/misc/TiffImagePlugin.py +838 -0
- PYME/misc/VolProj.py +144 -0
- PYME/misc/__init__.py +25 -0
- PYME/misc/aligned_array.py +10 -0
- PYME/misc/aniso.py +29 -0
- PYME/misc/big_sur_fix.py +20 -0
- PYME/misc/check_for_updates.py +139 -0
- PYME/misc/colormaps.py +199 -0
- PYME/misc/colorsys.py +201 -0
- PYME/misc/computerName.py +33 -0
- PYME/misc/coverslipGeometries.py +60 -0
- PYME/misc/cython_numpy_monkey.py +40 -0
- PYME/misc/depGraph.py +406 -0
- PYME/misc/dirSize.py +31 -0
- PYME/misc/diskCleanup.py +64 -0
- PYME/misc/djangoRecarray.py +57 -0
- PYME/misc/django_widgets.py +27 -0
- PYME/misc/editgrid.py +101 -0
- PYME/misc/eventSpy.py +122 -0
- PYME/misc/exceptions.py +30 -0
- PYME/misc/facsPlot.py +89 -0
- PYME/misc/fbpIcons.py +90 -0
- PYME/misc/fftw_compat.py +28 -0
- PYME/misc/findCycles.py +91 -0
- PYME/misc/fortran_interrupt_defeat.py +43 -0
- PYME/misc/glassModel.py +64 -0
- PYME/misc/hash32.py +41 -0
- PYME/misc/hillcurve.py +88 -0
- PYME/misc/hybrid_ns.py +84 -0
- PYME/misc/kdFit.py +72 -0
- PYME/misc/locate.py +10 -0
- PYME/misc/memUsage.py +65 -0
- PYME/misc/memmap.py +52 -0
- PYME/misc/mock_ctypes.py +28 -0
- PYME/misc/mock_traits.py +59 -0
- PYME/misc/mock_traitsui.py +29 -0
- PYME/misc/pyme_zeroconf.py +287 -0
- PYME/misc/pyro_tracebacks.py +32 -0
- PYME/misc/read_agf.py +605 -0
- PYME/misc/sffake.py +16 -0
- PYME/misc/snells.py +71 -0
- PYME/misc/sqlite_ns.py +132 -0
- PYME/misc/sqlitendarray.py +56 -0
- PYME/misc/taskDef.py +44 -0
- PYME/misc/tempDB.py +56 -0
- PYME/misc/wxThread.py +33 -0
- PYME/misc/zemax.py +259 -0
- PYME/misc/zernike.py +151 -0
- PYME/pyme_warnings.py +70 -0
- PYME/recipes/Recipes/STEDDeconvolution.yaml +14 -0
- PYME/recipes/Recipes/STEDMinistacks.yaml +15 -0
- PYME/recipes/Recipes/STED_COP3.yaml +25 -0
- PYME/recipes/Recipes/blobMeas.yaml +19 -0
- PYME/recipes/Recipes/blobMeas2.yaml +37 -0
- PYME/recipes/Recipes/blobMeasSR.yaml +66 -0
- PYME/recipes/Recipes/coloc.yaml +43 -0
- PYME/recipes/Recipes/coloc_roi_masked.yaml +46 -0
- PYME/recipes/Recipes/multiblob.yaml +38 -0
- PYME/recipes/Recipes/pre_particleTracking.yaml +30 -0
- PYME/recipes/Recipes/segmentAndMeasureLoci.yaml +41 -0
- PYME/recipes/__init__.py +6 -0
- PYME/recipes/acquisition.py +158 -0
- PYME/recipes/bakeshop.py +32 -0
- PYME/recipes/base.py +1393 -0
- PYME/recipes/batchProcess.py +147 -0
- PYME/recipes/filters.py +394 -0
- PYME/recipes/fudges.py +41 -0
- PYME/recipes/graphing.py +108 -0
- PYME/recipes/html_recipe_editor.py +90 -0
- PYME/recipes/localisations.py +1386 -0
- PYME/recipes/machine_learning.py +286 -0
- PYME/recipes/measurement.py +1062 -0
- PYME/recipes/modules.py +50 -0
- PYME/recipes/multiview.py +435 -0
- PYME/recipes/output.py +667 -0
- PYME/recipes/pointcloud.py +510 -0
- PYME/recipes/processing.py +2954 -0
- PYME/recipes/pyramid.py +206 -0
- PYME/recipes/recipe.py +965 -0
- PYME/recipes/recipeGui.py +1029 -0
- PYME/recipes/recipeLayout.py +499 -0
- PYME/recipes/runRecipe.py +140 -0
- PYME/recipes/setup.py +37 -0
- PYME/recipes/simulation.py +91 -0
- PYME/recipes/skfilters.py +143 -0
- PYME/recipes/surface_fitting.py +776 -0
- PYME/recipes/tablefilters.py +275 -0
- PYME/recipes/tracking.py +343 -0
- PYME/recipes/traits.py +157 -0
- PYME/recipes/vertical_recipe_display.py +373 -0
- PYME/resources/__init__.py +23 -0
- PYME/resources/config_template/distributor.yaml +14 -0
- PYME/resources/config_template/nodeserver.yaml +15 -0
- PYME/resources/icons/LUT.png +0 -0
- PYME/resources/icons/YSM_Shield.png +0 -0
- PYME/resources/icons/YSM_logo.png +0 -0
- PYME/resources/icons/arrow.png +0 -0
- PYME/resources/icons/crosshairs.png +0 -0
- PYME/resources/icons/line_select.png +0 -0
- PYME/resources/icons/logo_fmhs.png +0 -0
- PYME/resources/icons/media-playback-pause.png +0 -0
- PYME/resources/icons/media-playback-start.png +0 -0
- PYME/resources/icons/media-skip-backward.png +0 -0
- PYME/resources/icons/media-skip-forward.png +0 -0
- PYME/resources/icons/mesh.png +0 -0
- PYME/resources/icons/mesh.svg +28 -0
- PYME/resources/icons/pmacquire.icns +0 -0
- PYME/resources/icons/pmacquire.ico +0 -0
- PYME/resources/icons/pmanal.icns +0 -0
- PYME/resources/icons/pmanal.ico +0 -0
- PYME/resources/icons/pmvis.icns +0 -0
- PYME/resources/icons/pmvis.ico +0 -0
- PYME/resources/icons/points.png +0 -0
- PYME/resources/icons/points.svg +16 -0
- PYME/resources/icons/pymeLogo.png +0 -0
- PYME/resources/icons/rect_select.png +0 -0
- PYME/resources/icons/squiggle_select.png +0 -0
- PYME/resources/icons/text3800.icns +0 -0
- PYME/resources/icons/text3800.ico +0 -0
- PYME/resources/icons/text3800.png +0 -0
- PYME/resources/icons/text3800.tif +0 -0
- PYME/resources/icons/text5377.icns +0 -0
- PYME/resources/icons/text5377.ico +0 -0
- PYME/resources/icons/text5377.png +0 -0
- PYME/resources/icons/tracks.png +0 -0
- PYME/resources/icons/tracks.svg +19 -0
- PYME/resources/icons/view-front.png +0 -0
- PYME/resources/icons/view-home.png +0 -0
- PYME/resources/icons/view-right.png +0 -0
- PYME/resources/icons/view-top.png +0 -0
- PYME/resources/test_datasets/README.md +8 -0
- PYME/resources/test_datasets/t_im.tif +0 -0
- PYME/resources/test_datasets/test_csv.csv +4282 -0
- PYME/resources/test_datasets/test_hdf.hdf +0 -0
- PYME/resources/web/rule_inline.html +21 -0
- PYME/resources/web/ruleserver.html +75 -0
- PYME/resources/web/static/bootstrap.min.js +7 -0
- PYME/resources/web/static/cluster.css +137 -0
- PYME/resources/web/static/css/bootstrap-theme.css +587 -0
- PYME/resources/web/static/css/bootstrap-theme.min.css +6 -0
- PYME/resources/web/static/css/bootstrap.css +6757 -0
- PYME/resources/web/static/css/bootstrap.min.css +6 -0
- PYME/resources/web/static/css/flat_design_colors_full.css +306 -0
- PYME/resources/web/static/css/recipe_editor.css +106 -0
- PYME/resources/web/static/dashboard.css +106 -0
- PYME/resources/web/static/fileselector.js +77 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.eot +0 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.svg +288 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.ttf +0 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.woff +0 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.woff2 +0 -0
- PYME/resources/web/static/images/osd_flat/LICENSE.txt +116 -0
- PYME/resources/web/static/images/osd_flat/README.md +9 -0
- PYME/resources/web/static/images/osd_flat/fullpage_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/fullpage_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/fullpage_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/fullpage_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_rest.png +0 -0
- PYME/resources/web/static/jigna-vue-bare.js +1395 -0
- PYME/resources/web/static/jquery.min.js +2 -0
- PYME/resources/web/static/jstree.min.js +6 -0
- PYME/resources/web/static/openSeadragonGL.js +102 -0
- PYME/resources/web/static/openseadragon-svg-overlay.js +103 -0
- PYME/resources/web/static/openseadragon-viewerinputhook.min.js +6 -0
- PYME/resources/web/static/openseadragon.min.js +9 -0
- PYME/resources/web/static/recipe_editor.js +119 -0
- PYME/resources/web/static/shaders/fragment/lut.glsl +20 -0
- PYME/resources/web/static/shaders/fragment/none.glsl +17 -0
- PYME/resources/web/static/shaders/fragment/outline.glsl +43 -0
- PYME/resources/web/static/shaders/fragment/rect.glsl +7 -0
- PYME/resources/web/static/shaders/fragment/sobel3.glsl +71 -0
- PYME/resources/web/static/shaders/fragment/white.glsl +4 -0
- PYME/resources/web/static/shaders/vertex/rect.glsl +22 -0
- PYME/resources/web/static/shaders/vertex/square.glsl +9 -0
- PYME/resources/web/static/utils.js +3 -0
- PYME/resources/web/static/viaWebGL.js +217 -0
- PYME/setup.py +109 -0
- PYME/setup_exe.py +57 -0
- PYME/simulation/__init__.py +0 -0
- PYME/simulation/locify.py +319 -0
- PYME/simulation/pointsets.py +279 -0
- PYME/simulation/wormlike.py +99 -0
- PYME/simulation/wormlike2.py +198 -0
- PYME/simulation/wormlike3.py +197 -0
- PYME/simulation/wormlike_curled.py +104 -0
- PYME/tileviewer/__init__.py +0 -0
- PYME/tileviewer/templates/roi_list.html +12 -0
- PYME/tileviewer/templates/tileviewer.html +247 -0
- PYME/tileviewer/tileviewer.py +207 -0
- PYME/ui/AUIFrame.py +279 -0
- PYME/ui/MetadataTree.py +469 -0
- PYME/ui/__init__.py +4 -0
- PYME/ui/about_dlg.py +29 -0
- PYME/ui/autoFoldPanel.py +643 -0
- PYME/ui/cascading_layout.py +45 -0
- PYME/ui/crop_dialog.py +145 -0
- PYME/ui/custom_traits_editors.py +508 -0
- PYME/ui/editFilterDialog.py +80 -0
- PYME/ui/editList.py +40 -0
- PYME/ui/fastGraph.py +240 -0
- PYME/ui/filterPane.py +348 -0
- PYME/ui/histLimits.py +543 -0
- PYME/ui/layerFoldPanel.py +232 -0
- PYME/ui/manualFoldPanel.py +870 -0
- PYME/ui/mytimer.py +116 -0
- PYME/ui/patch_traitsui.py +151 -0
- PYME/ui/progress.py +160 -0
- PYME/ui/recArrayView.py +145 -0
- PYME/ui/selection.py +71 -0
- PYME/ui/shell.py +37 -0
- PYME/ui/traitsui_constants.py +91 -0
- PYME/ui/wx_compat.py +43 -0
- PYME/update_version.py +159 -0
- PYME/util/__init__.py +3 -0
- PYME/util/authenticate.py +164 -0
- PYME/util/execfile.py +21 -0
- PYME/util/fProfile/__init__.py +1 -0
- PYME/util/fProfile/convertProfile.py +117 -0
- PYME/util/fProfile/fProfile.py +162 -0
- PYME/util/fProfile/html/404.html +60 -0
- PYME/util/fProfile/html/css/main.css +258 -0
- PYME/util/fProfile/html/css/normalize.css +427 -0
- PYME/util/fProfile/html/css/normalize.min.css +1 -0
- PYME/util/fProfile/html/index.html +64 -0
- PYME/util/fProfile/html/js/main.js +492 -0
- PYME/util/fProfile/html/js/plugins.js +24 -0
- PYME/util/fProfile/html/js/vendor/crossfilter.min.js +1 -0
- PYME/util/fProfile/html/js/vendor/jquery-1.11.2.min.js +4 -0
- PYME/util/fProfile/html/js/vendor/modernizr-2.8.3.min.js +4 -0
- PYME/util/fProfile/html/js/vendor/tinycolor-min.js +4 -0
- PYME/util/fProfile/viewProfile.py +30 -0
- PYME/util/install_dependencies.py +95 -0
- PYME/util/log_verbosity.py +11 -0
- PYME/util/mProfile/__init__.py +1 -0
- PYME/util/mProfile/colorize_db_t.py +297 -0
- PYME/util/mProfile/mProfile.py +164 -0
- PYME/util/mProfile/p.py +101 -0
- PYME/util/mProfile/tProfile.py +165 -0
- PYME/util/pymelauncher.py +47 -0
- PYME/util/shmarray/__init__.py +0 -0
- PYME/util/shmarray/shmTest.py +74 -0
- PYME/util/shmarray/shmarray.py +165 -0
- PYME/util/webframework.py +365 -0
- PYME/version.py +65 -0
- python_microscopy-25.6.5.dist-info/METADATA +68 -0
- python_microscopy-25.6.5.dist-info/RECORD +1277 -0
- python_microscopy-25.6.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,2287 @@
|
|
|
1
|
+
# -*- coding: utf8 -*-
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Regular, boundary, and max vertex valences. These need tp be
|
|
6
|
+
VALENCE = 6
|
|
7
|
+
BOUNDARY_VALENCE = 4
|
|
8
|
+
NEIGHBORSIZE = 20 # Note this must match NEIGHBORSIZE in triangle_mesh_utils.h
|
|
9
|
+
|
|
10
|
+
USE_C = True
|
|
11
|
+
|
|
12
|
+
if USE_C:
|
|
13
|
+
from PYME.experimental import triangle_mesh_utils
|
|
14
|
+
|
|
15
|
+
# def pack_edges(arr, axis=1):
|
|
16
|
+
# """
|
|
17
|
+
# Stores edges as a single unique integer.
|
|
18
|
+
|
|
19
|
+
# NOTE: that this implictly caps the mesh at 2**32
|
|
20
|
+
# vertices.
|
|
21
|
+
# """
|
|
22
|
+
# arr = np.sort(arr, axis=axis)
|
|
23
|
+
# res = ((arr[:,0].astype(np.uint64)) << 32)
|
|
24
|
+
# res += arr[:,1].astype(np.uint64)
|
|
25
|
+
|
|
26
|
+
# return res
|
|
27
|
+
|
|
28
|
+
MAX_VERTEX_COUNT = 2**64
|
|
29
|
+
|
|
30
|
+
def fast_3x3_cross(a,b):
|
|
31
|
+
# Index only once
|
|
32
|
+
a0 = a[0]
|
|
33
|
+
a1 = a[1]
|
|
34
|
+
a2 = a[2]
|
|
35
|
+
b0 = b[0]
|
|
36
|
+
b1 = b[1]
|
|
37
|
+
b2 = b[2]
|
|
38
|
+
|
|
39
|
+
x = a1*b2 - a2*b1
|
|
40
|
+
y = a2*b0 - a0*b2
|
|
41
|
+
z = a0*b1 - a1*b0
|
|
42
|
+
|
|
43
|
+
vec = np.array([x,y,z])
|
|
44
|
+
return vec
|
|
45
|
+
|
|
46
|
+
HALFEDGE_DTYPE = np.dtype([('vertex', 'i4'), ('face', 'i4'), ('twin', 'i4'), ('next', 'i4'), ('prev', 'i4'), ('length', 'f4'), ('component', 'i4')])
|
|
47
|
+
FACE_DTYPE = np.dtype([('halfedge', 'i4'), ('normal', '3f4'), ('area', 'f4'), ('component', 'i4')])
|
|
48
|
+
# Note that VERTEX_DTYPE neighbors size must match NEIGHBORSIZE
|
|
49
|
+
VERTEX_DTYPE = np.dtype([('position', '3f4'), ('normal', '3f4'), ('halfedge', 'i4'), ('valence', 'i4'), ('neighbors', '20i4'), ('component', 'i4')])
|
|
50
|
+
|
|
51
|
+
LOOP_ALPHA_FACTOR = (np.log(13)-np.log(3))/12
|
|
52
|
+
|
|
53
|
+
class TrianglesBase(object):
|
|
54
|
+
"""
|
|
55
|
+
Base class to let VisGUI layers detect objects which are a bunch of triangles
|
|
56
|
+
|
|
57
|
+
a derived class should provide suitable attributes/properties for vertices, faces, face_normals,
|
|
58
|
+
and vertex_normals.
|
|
59
|
+
|
|
60
|
+
See Also: Tesselation class in PYME.recipes.pointcloud
|
|
61
|
+
|
|
62
|
+
TODO - move me out of here?
|
|
63
|
+
TODO - provide default implementations for face_normals and vertex_normals?
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
vertices=None
|
|
67
|
+
faces=None
|
|
68
|
+
face_normals=None
|
|
69
|
+
vertex_normals = None
|
|
70
|
+
|
|
71
|
+
def keys(self):
|
|
72
|
+
"""
|
|
73
|
+
returns the names of possible vertex attributes
|
|
74
|
+
"""
|
|
75
|
+
raise NotImplementedError('Over-ride in derived class')
|
|
76
|
+
|
|
77
|
+
def __getitem__(self, item):
|
|
78
|
+
"""
|
|
79
|
+
returns a given vertex attribute
|
|
80
|
+
"""
|
|
81
|
+
raise NotImplementedError('Over-ride in derived class')
|
|
82
|
+
|
|
83
|
+
class TriangleMesh(TrianglesBase):
|
|
84
|
+
def __init__(self, vertices=None, faces=None, mesh=None, **kwargs):
|
|
85
|
+
"""
|
|
86
|
+
Base class for triangle meshes stored using halfedge data structure.
|
|
87
|
+
Expects STL-like input.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
vertices : np.array
|
|
92
|
+
N x 3 array of Euclidean points.
|
|
93
|
+
faces : np .array
|
|
94
|
+
M x 3 array of vertex indices indicating triangle connectivity.
|
|
95
|
+
Expects STL redundancy.
|
|
96
|
+
debug : bool
|
|
97
|
+
Print debug statements (assumes manifold mesh).
|
|
98
|
+
"""
|
|
99
|
+
if mesh is not None:
|
|
100
|
+
import copy
|
|
101
|
+
|
|
102
|
+
self._vertices = np.copy(mesh._vertices)
|
|
103
|
+
self._vertex_vacancies = copy.copy(mesh._vertex_vacancies)
|
|
104
|
+
|
|
105
|
+
self._faces = np.copy(mesh._faces)
|
|
106
|
+
self._face_vacancies = copy.copy(mesh._face_vacancies)
|
|
107
|
+
|
|
108
|
+
self._halfedges = np.copy(mesh._halfedges)
|
|
109
|
+
self._halfedge_vacancies = copy.copy(mesh._halfedge_vacancies)
|
|
110
|
+
else:
|
|
111
|
+
self._vertices = np.zeros(vertices.shape[0], dtype=VERTEX_DTYPE)
|
|
112
|
+
self._vertices[:] = -1 # initialize everything to -1 to start with
|
|
113
|
+
self._vertices['position'] = vertices
|
|
114
|
+
self._vertex_vacancies = []
|
|
115
|
+
|
|
116
|
+
self._faces = None # Contains a pointer to one halfedge associated with each face
|
|
117
|
+
self._face_vacancies = []
|
|
118
|
+
|
|
119
|
+
# Halfedges
|
|
120
|
+
self._halfedges = None
|
|
121
|
+
self._halfedge_vacancies = []
|
|
122
|
+
|
|
123
|
+
print('initializing halfedges ...')
|
|
124
|
+
print('vertices.shape = %s, faces.shape = %s' % (vertices.shape, faces.shape))
|
|
125
|
+
if vertices.shape[0] >= MAX_VERTEX_COUNT:
|
|
126
|
+
raise RuntimeError('Maximum vertex count is %d, mesh has %d' % (MAX_VERTEX_COUNT, vertices.shape[0]))
|
|
127
|
+
self._initialize_halfedges(vertices, faces)
|
|
128
|
+
print('done initializing halfedges')
|
|
129
|
+
|
|
130
|
+
# Representation of faces by triplets of vertices
|
|
131
|
+
self._faces_by_vertex = None
|
|
132
|
+
|
|
133
|
+
# loop subdivision vars
|
|
134
|
+
self._loop_subdivision_flip_edges = []
|
|
135
|
+
self._loop_subdivision_new_vertices = []
|
|
136
|
+
|
|
137
|
+
# Singular edges
|
|
138
|
+
self._singular_edges = None
|
|
139
|
+
self._singular_vertices = None
|
|
140
|
+
|
|
141
|
+
# Populate the normals
|
|
142
|
+
self.face_normals
|
|
143
|
+
self.vertex_normals
|
|
144
|
+
|
|
145
|
+
# Properties we can visualize
|
|
146
|
+
self.vertex_properties = ['x', 'y', 'z', 'component', 'boundary', 'singular']
|
|
147
|
+
|
|
148
|
+
self.fix_boundary = True # Hold boundary edges in place
|
|
149
|
+
self.debug = False # Print debug statements
|
|
150
|
+
|
|
151
|
+
# Is the mesh manifold?
|
|
152
|
+
self._manifold = None
|
|
153
|
+
|
|
154
|
+
# Set debug, fix_boundary, etc.
|
|
155
|
+
for key, value in kwargs.items():
|
|
156
|
+
setattr(self, key, value)
|
|
157
|
+
|
|
158
|
+
def __getitem__(self, k):
|
|
159
|
+
# this defers evaluation of the properties until we actually access them,
|
|
160
|
+
# as opposed to the mappings which stored the values on class creation.
|
|
161
|
+
try:
|
|
162
|
+
res = getattr(self, k)
|
|
163
|
+
except AttributeError:
|
|
164
|
+
raise KeyError('Key %s not defined' % k)
|
|
165
|
+
|
|
166
|
+
return res
|
|
167
|
+
|
|
168
|
+
def __copy__(self):
|
|
169
|
+
return type(self)(mesh=self)
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def from_stl(cls, filename, **kwargs):
|
|
173
|
+
"""
|
|
174
|
+
Read from an STL file.
|
|
175
|
+
"""
|
|
176
|
+
from PYME.IO.FileUtils import stl
|
|
177
|
+
|
|
178
|
+
# Load an STL from file
|
|
179
|
+
triangles_stl = stl.load_stl_binary(filename)
|
|
180
|
+
|
|
181
|
+
# Call from_np_stl on the file stream
|
|
182
|
+
return cls.from_np_stl(triangles_stl, **kwargs)
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
def from_ply(cls, filename, **kwargs):
|
|
186
|
+
"""
|
|
187
|
+
Read from PLY file.
|
|
188
|
+
"""
|
|
189
|
+
from PYME.IO.FileUtils import ply
|
|
190
|
+
|
|
191
|
+
# Load a PLY from file
|
|
192
|
+
vertices, faces, _ = ply.load_ply(filename)
|
|
193
|
+
|
|
194
|
+
return cls(vertices, faces, **kwargs)
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def from_np_stl(cls, triangles_stl, **kwargs):
|
|
198
|
+
"""
|
|
199
|
+
Read from an already-loaded STL stream.
|
|
200
|
+
"""
|
|
201
|
+
vertices_raw = np.vstack((triangles_stl['vertex0'],
|
|
202
|
+
triangles_stl['vertex1'],
|
|
203
|
+
triangles_stl['vertex2']))
|
|
204
|
+
vertices, faces_raw = np.unique(vertices_raw,
|
|
205
|
+
return_inverse=True,
|
|
206
|
+
axis=0)
|
|
207
|
+
faces = faces_raw.reshape(faces_raw.shape[0] // 3, 3, order='F')
|
|
208
|
+
|
|
209
|
+
print('Data munged to vertices, faces')
|
|
210
|
+
return cls(vertices, faces, **kwargs)
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def x(self):
|
|
214
|
+
return self.vertices[:,0]
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def y(self):
|
|
218
|
+
return self.vertices[:,1]
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def z(self):
|
|
222
|
+
return self.vertices[:,2]
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def component(self):
|
|
226
|
+
# if np.all(self._vertices['component'] == -1):
|
|
227
|
+
self.find_connected_components()
|
|
228
|
+
return self._vertices['component']
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def vertices(self):
|
|
232
|
+
return self._vertices['position']
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def faces(self):
|
|
236
|
+
"""
|
|
237
|
+
Returns faces by vertex for rendering and STL applications.
|
|
238
|
+
"""
|
|
239
|
+
if self._faces_by_vertex is None:
|
|
240
|
+
faces = self._faces['halfedge'][self._faces['halfedge'] != -1]
|
|
241
|
+
v0 = self._halfedges['vertex'][self._halfedges['prev'][faces]]
|
|
242
|
+
v1 = self._halfedges['vertex'][faces]
|
|
243
|
+
v2 = self._halfedges['vertex'][self._halfedges['next'][faces]]
|
|
244
|
+
self._faces_by_vertex = np.vstack([v0, v1, v2]).T
|
|
245
|
+
return self._faces_by_vertex
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def boundary(self):
|
|
249
|
+
nn = self._vertices['neighbors']
|
|
250
|
+
nn_mask = (nn!=-1)
|
|
251
|
+
return np.any((self._halfedges['twin'][nn] == -1)*nn_mask, axis=1)
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def singular(self):
|
|
255
|
+
self._singular_edges = None
|
|
256
|
+
self._singular_vertices = None
|
|
257
|
+
singular_vertices = np.zeros_like(self._vertices['halfedge'])
|
|
258
|
+
if len(self.singular_vertices) > 0:
|
|
259
|
+
singular_vertices[np.array(self.singular_vertices)] = 1
|
|
260
|
+
return singular_vertices
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def singular_edges(self):
|
|
264
|
+
"""
|
|
265
|
+
Get a list of singular edges. A singular edge is any edge shared by
|
|
266
|
+
more than two faces.
|
|
267
|
+
"""
|
|
268
|
+
if self._singular_edges is None:
|
|
269
|
+
edges = np.vstack([self._halfedges['vertex'], self._halfedges[self._halfedges['prev']]['vertex']]).T
|
|
270
|
+
edges = edges[edges[:,0] != -1] # Drop non-edges
|
|
271
|
+
# packed_edges = pack_edges(edges)
|
|
272
|
+
packed_edges = np.sort(edges, axis=1)
|
|
273
|
+
e, c = np.unique(packed_edges, return_counts=True, axis=0)
|
|
274
|
+
singular_packed_edges = e[c>2]
|
|
275
|
+
singular_edges = []
|
|
276
|
+
for singular_packed_edge in singular_packed_edges:
|
|
277
|
+
singular_edges.extend(list(np.where(packed_edges[:, None] == singular_packed_edge)[0]))
|
|
278
|
+
|
|
279
|
+
singular_edges.extend(self._halfedges['twin'][singular_edges])
|
|
280
|
+
self._singular_edges = list(set(singular_edges) - set([-1]))
|
|
281
|
+
|
|
282
|
+
return self._singular_edges
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def singular_vertices(self):
|
|
286
|
+
"""
|
|
287
|
+
Get a list of singular vertices. This includes endpoints of singular
|
|
288
|
+
edges, and isolated singular vertices. Isolated singular vertices
|
|
289
|
+
have more than one 1-neighbor ring.
|
|
290
|
+
"""
|
|
291
|
+
if self._singular_vertices is None:
|
|
292
|
+
# Mark vertices that are endpoints of these edges
|
|
293
|
+
singular_vertices = list(set(list(np.hstack([self._halfedges['vertex'][self.singular_edges], self._halfedges['vertex'][self._halfedges['twin'][self.singular_edges]]]))))
|
|
294
|
+
|
|
295
|
+
# Mark isolated singular vertices
|
|
296
|
+
for _vertex in np.arange(self._vertices.shape[0]):
|
|
297
|
+
if self._vertices['halfedge'][_vertex] == -1:
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
# If the number of elements in the 1-neighbor ring does not match
|
|
301
|
+
# the number of halfedges pointing to a vertex, we have an isolated
|
|
302
|
+
# singular vertex (note this requires the C code version of
|
|
303
|
+
# update_vertex_neighbors, which reverses direction upon hitting
|
|
304
|
+
# an edge to ensure a more accurate neighborhood)
|
|
305
|
+
n_incident = np.flatnonzero(self._halfedges['vertex'] == _vertex).size
|
|
306
|
+
n_neighbors = np.flatnonzero(self._vertices['neighbors'][_vertex] != -1).size
|
|
307
|
+
if n_neighbors != n_incident:
|
|
308
|
+
singular_vertices.append(_vertex)
|
|
309
|
+
|
|
310
|
+
# Remove duplicates
|
|
311
|
+
self._singular_vertices = list(set(singular_vertices) - set([-1]))
|
|
312
|
+
|
|
313
|
+
return self._singular_vertices
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def face_normals(self):
|
|
317
|
+
"""
|
|
318
|
+
Return the normal of each triangular face.
|
|
319
|
+
"""
|
|
320
|
+
if np.all(self._faces['normal'] == -1):
|
|
321
|
+
if USE_C:
|
|
322
|
+
triangle_mesh_utils.c_update_face_normals(list(np.arange(len(self._faces)).astype(np.int32)), self._halfedges, self._vertices, self._faces)
|
|
323
|
+
else:
|
|
324
|
+
v2 = self._vertices['position'][self._halfedges['vertex'][self._halfedges['prev'][self._faces['halfedge']]]]
|
|
325
|
+
v1 = self._vertices['position'][self._halfedges['vertex'][self._faces['halfedge']]]
|
|
326
|
+
v0 = self._vertices['position'][self._halfedges['vertex'][self._halfedges['next'][self._faces['halfedge']]]]
|
|
327
|
+
u = v2 - v1
|
|
328
|
+
v = v0 - v1
|
|
329
|
+
n = np.cross(u, v, axis=1)
|
|
330
|
+
nn = np.linalg.norm(n, axis=1)
|
|
331
|
+
self._faces['area'] = 0.5*nn
|
|
332
|
+
self._faces['normal'] = n/nn[:, None]
|
|
333
|
+
self._faces['normal'][np.isnan(self._faces['normal'])] = 0
|
|
334
|
+
self._faces['normal'][self._faces['halfedge'] == -1] = -1
|
|
335
|
+
return self._faces['normal'][self._faces['halfedge'] != -1]
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def face_areas(self):
|
|
339
|
+
"""
|
|
340
|
+
Return the area of each triangular face.
|
|
341
|
+
"""
|
|
342
|
+
if np.all(self._faces['area'] == -1):
|
|
343
|
+
self._faces['normal'][:] = -1
|
|
344
|
+
self.face_normals
|
|
345
|
+
return self._faces['area']
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def vertex_neighbors(self):
|
|
349
|
+
"""
|
|
350
|
+
Return the up to NEIGHBORSIZE neighbors of each vertex.
|
|
351
|
+
"""
|
|
352
|
+
if np.all(self._vertices['neighbors'] == -1):
|
|
353
|
+
if USE_C:
|
|
354
|
+
triangle_mesh_utils.c_update_vertex_neighbors(list(np.arange(len(self._vertices)).astype(np.int32)), self._halfedges, self._vertices, self._faces)
|
|
355
|
+
else:
|
|
356
|
+
for v_idx in np.arange(len(self._vertices)):
|
|
357
|
+
_orig = self._vertices['halfedge'][v_idx]
|
|
358
|
+
_curr = _orig
|
|
359
|
+
|
|
360
|
+
curr_edge = self._halfedges[_curr]
|
|
361
|
+
_twin = curr_edge['twin']
|
|
362
|
+
twin_edge = self._halfedges[_twin]
|
|
363
|
+
|
|
364
|
+
self._vertices['normal'][v_idx] = 0
|
|
365
|
+
|
|
366
|
+
i = 0
|
|
367
|
+
self._vertices['valence'][v_idx] = 0
|
|
368
|
+
while True:
|
|
369
|
+
if _curr == -1:
|
|
370
|
+
break
|
|
371
|
+
_vertex = curr_edge['vertex']
|
|
372
|
+
_face = curr_edge['face']
|
|
373
|
+
if (i < NEIGHBORSIZE):
|
|
374
|
+
self._vertices['neighbors'][v_idx, i] = _curr
|
|
375
|
+
n = self._faces['normal'][_face]
|
|
376
|
+
a = self._faces['area'][_face]
|
|
377
|
+
self._vertices['normal'][v_idx] += n*a
|
|
378
|
+
|
|
379
|
+
l = self._vertices['position'][_vertex] - self._vertices['position'][self._halfedges['vertex'][curr_edge['prev']]]
|
|
380
|
+
curr_edge['length'] = np.sqrt((l*l).sum())
|
|
381
|
+
|
|
382
|
+
_curr = twin_edge['next']
|
|
383
|
+
curr_edge = self._halfedges[_curr]
|
|
384
|
+
if _twin == -1:
|
|
385
|
+
break
|
|
386
|
+
_twin = curr_edge['twin']
|
|
387
|
+
twin_edge = self._halfedges[_twin]
|
|
388
|
+
|
|
389
|
+
i += 1
|
|
390
|
+
if (_curr == _orig):
|
|
391
|
+
break
|
|
392
|
+
self._vertices['valence'][v_idx] = i
|
|
393
|
+
nn = np.linalg.norm(self._vertices['normal'], axis=1)
|
|
394
|
+
self._vertices['normal'] /= nn[:, None]
|
|
395
|
+
self._vertices['normal'][np.isnan(self._vertices['normal'])] = 0
|
|
396
|
+
self._vertices['normal'][self._vertices['halfedge'] == -1] = -1
|
|
397
|
+
|
|
398
|
+
return self._vertices['neighbors']
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def vertex_normals(self):
|
|
402
|
+
"""
|
|
403
|
+
Return the normal of each vertex.
|
|
404
|
+
"""
|
|
405
|
+
if np.all(self._vertices['normal'] == -1):
|
|
406
|
+
self._vertices['neighbors'][:] = -1
|
|
407
|
+
self.vertex_neighbors
|
|
408
|
+
return self._vertices['normal']
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def valences(self):
|
|
412
|
+
"""
|
|
413
|
+
Return the valence of each vertex.
|
|
414
|
+
"""
|
|
415
|
+
if np.all(self._vertices['valence'] == -1):
|
|
416
|
+
self._vertices['neighbors'][:] = -1
|
|
417
|
+
self.vertex_neighbors
|
|
418
|
+
return self._vertices['valence']
|
|
419
|
+
|
|
420
|
+
@property
|
|
421
|
+
def manifold(self):
|
|
422
|
+
"""
|
|
423
|
+
Checks if the mesh is manifold: Is every edge is shared by exactly two
|
|
424
|
+
triangles?
|
|
425
|
+
|
|
426
|
+
TODO: Check that we don't have any isolated singular vertices, which
|
|
427
|
+
also make the mesh non-manifold.
|
|
428
|
+
"""
|
|
429
|
+
# edges = np.vstack([self._halfedges['vertex'], self._halfedges[self._halfedges['prev']]['vertex']]).T
|
|
430
|
+
# edges[edges[:, 0] != -1]
|
|
431
|
+
# edges = np.vstack([self.faces[:,[0,1]], self.faces[:,[1,2]], self.faces[:,[2,0]]])
|
|
432
|
+
# packed_edges = pack_edges(edges)
|
|
433
|
+
# _, c = np.unique(packed_edges, return_counts=True)
|
|
434
|
+
|
|
435
|
+
# return np.all(c==2)
|
|
436
|
+
|
|
437
|
+
if self._manifold is None:
|
|
438
|
+
self._singular_edges = None
|
|
439
|
+
self._singular_vertices = None
|
|
440
|
+
vertex_mask = (self._halfedges['vertex'] != -1)
|
|
441
|
+
twin_mask = (self._halfedges['twin'] == -1)
|
|
442
|
+
boundary_check = ((np.flatnonzero(vertex_mask & twin_mask).size) == 0)
|
|
443
|
+
self._manifold = (len(self.singular_vertices) == 0) & boundary_check
|
|
444
|
+
|
|
445
|
+
return self._manifold
|
|
446
|
+
|
|
447
|
+
def keys(self):
|
|
448
|
+
return list(self.vertex_properties)
|
|
449
|
+
|
|
450
|
+
def _initialize_halfedges(self, vertices, faces):
|
|
451
|
+
"""
|
|
452
|
+
Accepts unordered vertices, indices parameterization of a triangular
|
|
453
|
+
mesh from an STL file and converts to topologically-connected halfedge
|
|
454
|
+
representation.
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
vertices : np.array
|
|
459
|
+
N x 3 array of Euclidean points.
|
|
460
|
+
faces : np .array
|
|
461
|
+
M x 3 array of vertex indices indicating triangle connectivity.
|
|
462
|
+
Expects STL redundancy.
|
|
463
|
+
"""
|
|
464
|
+
if vertices is not None and faces is not None:
|
|
465
|
+
# Unordered halfedges
|
|
466
|
+
edges = np.vstack([faces[:,[0,1]], faces[:,[1,2]], faces[:,[2,0]]])
|
|
467
|
+
|
|
468
|
+
# Now order them...
|
|
469
|
+
n_faces = len(faces)
|
|
470
|
+
n_edges = len(edges)
|
|
471
|
+
j = np.arange(n_faces)
|
|
472
|
+
|
|
473
|
+
self._halfedges = np.zeros(n_edges, dtype=HALFEDGE_DTYPE)
|
|
474
|
+
self._halfedges[:] = -1 # initialize everything to -1 to start with
|
|
475
|
+
|
|
476
|
+
self._halfedges['vertex'] = edges[:, 1]
|
|
477
|
+
|
|
478
|
+
self._faces = np.zeros(n_faces, dtype=FACE_DTYPE)
|
|
479
|
+
self._faces[:] = -1 # initialize everything to -1 to start with
|
|
480
|
+
|
|
481
|
+
# Create a unique handle for each edge
|
|
482
|
+
# edges_packed = pack_edges(edges)
|
|
483
|
+
|
|
484
|
+
# Sort the edges lo->hi so we can arrange them uniquely
|
|
485
|
+
# Convert to list of tuples for use with dictionary
|
|
486
|
+
edges_packed = [tuple(e) for e in np.sort(edges, axis=1)]
|
|
487
|
+
|
|
488
|
+
# Account for multivalent edges
|
|
489
|
+
# edges_unique, edges_counts = np.unique(edges, return_counts=True, axis=0)
|
|
490
|
+
|
|
491
|
+
# print('iterating edges')
|
|
492
|
+
# # Use a dictionary to keep track of which edges are already assigned twins
|
|
493
|
+
# d = {}
|
|
494
|
+
# multivalent_edge = False
|
|
495
|
+
# multivalent_dict = {}
|
|
496
|
+
# for i, e in enumerate(edges_packed):
|
|
497
|
+
# if e in d:
|
|
498
|
+
# idx = d.pop(e)
|
|
499
|
+
# if self._halfedges['vertex'][idx] == self._halfedges['vertex'][i]:
|
|
500
|
+
# # Push it back in the queue and let's see if the next time we
|
|
501
|
+
# # pull it out we get the right matching edge.
|
|
502
|
+
# d[e] = idx
|
|
503
|
+
# # Trip the flag
|
|
504
|
+
# multivalent_edge = True
|
|
505
|
+
# multivalent_dict[e] = i
|
|
506
|
+
# continue
|
|
507
|
+
|
|
508
|
+
# self._halfedges['twin'][idx] = i
|
|
509
|
+
# self._halfedges['twin'][i] = idx
|
|
510
|
+
# else:
|
|
511
|
+
# d[e] = i
|
|
512
|
+
|
|
513
|
+
print('iterating edges')
|
|
514
|
+
# Use a dictionary to keep track of which edges are already assigned twins
|
|
515
|
+
d = {}
|
|
516
|
+
for i, e in enumerate(edges_packed):
|
|
517
|
+
if e in d:
|
|
518
|
+
idx = d.pop(e)
|
|
519
|
+
if self._halfedges['vertex'][idx] == self._halfedges['vertex'][i]:
|
|
520
|
+
# Don't assign a halfedge to multivalent edges
|
|
521
|
+
continue
|
|
522
|
+
|
|
523
|
+
self._halfedges['twin'][idx] = i
|
|
524
|
+
self._halfedges['twin'][i] = idx
|
|
525
|
+
else:
|
|
526
|
+
d[e] = i
|
|
527
|
+
|
|
528
|
+
# print('multivalent')
|
|
529
|
+
# # Go back and grab the remaining multivalent edges
|
|
530
|
+
# if multivalent_edge:
|
|
531
|
+
# n_multivalent = len(list(multivalent_dict.keys()))
|
|
532
|
+
# print('%d multivalent edges' % n_multivalent)
|
|
533
|
+
# for j_, k in enumerate(list(multivalent_dict.keys())):
|
|
534
|
+
# if (not (j_ % 10)):
|
|
535
|
+
# print('mv edge %d of %d' % (j_, n_multivalent))
|
|
536
|
+
#
|
|
537
|
+
# idx = multivalent_dict.pop(k)
|
|
538
|
+
# for i, e in enumerate(edges_packed):
|
|
539
|
+
# if e != idx:
|
|
540
|
+
# continue
|
|
541
|
+
#
|
|
542
|
+
# if self._halfedges['vertex'][idx] == self._halfedges['vertex'][i]:
|
|
543
|
+
# continue
|
|
544
|
+
#
|
|
545
|
+
# self._halfedges['twin'][idx] = i
|
|
546
|
+
# self._halfedges['twin'][i] = idx
|
|
547
|
+
# break
|
|
548
|
+
#
|
|
549
|
+
# print('multivalent done')
|
|
550
|
+
|
|
551
|
+
self._halfedges['face'] = np.hstack([j, j, j])
|
|
552
|
+
self._halfedges['next'] = np.hstack([j+n_faces, j+2*n_faces, j])
|
|
553
|
+
self._halfedges['prev'] = np.hstack([j+2*n_faces, j, j+n_faces])
|
|
554
|
+
self._halfedges['length'] = np.linalg.norm(self._vertices['position'][self._halfedges['vertex']] - self._vertices['position'][self._halfedges['vertex'][self._halfedges['prev']]], axis=1)
|
|
555
|
+
|
|
556
|
+
# Set the vertex halfedges (multiassignment, but should be fine)
|
|
557
|
+
self._vertices['halfedge'][self._halfedges['vertex']] = self._halfedges['next']
|
|
558
|
+
|
|
559
|
+
self._faces['halfedge'] = j # Faces are defined by associated halfedges
|
|
560
|
+
else:
|
|
561
|
+
raise ValueError('Mesh does not contain vertices and faces.')
|
|
562
|
+
|
|
563
|
+
def _update_face_normals(self, f_idxs):
|
|
564
|
+
"""
|
|
565
|
+
Recompute len(f_idxs) face normals.
|
|
566
|
+
|
|
567
|
+
Parameters
|
|
568
|
+
----------
|
|
569
|
+
f_idxs : list or np.array
|
|
570
|
+
List of face indices to recompute.
|
|
571
|
+
"""
|
|
572
|
+
|
|
573
|
+
if USE_C:
|
|
574
|
+
if isinstance(f_idxs, list):
|
|
575
|
+
f_idxs = np.int32(f_idxs)
|
|
576
|
+
triangle_mesh_utils.c_update_face_normals(f_idxs, self._halfedges, self._vertices, self._faces)
|
|
577
|
+
else:
|
|
578
|
+
for f_idx in f_idxs:
|
|
579
|
+
v2 = self._vertices['position'][self._halfedges['vertex'][self._halfedges['prev'][self._faces['halfedge'][f_idx]]]]
|
|
580
|
+
v1 = self._vertices['position'][self._halfedges['vertex'][self._faces['halfedge'][f_idx]]]
|
|
581
|
+
v0 = self._vertices['position'][self._halfedges['vertex'][self._halfedges['next'][self._faces['halfedge'][f_idx]]]]
|
|
582
|
+
u = v2 - v1
|
|
583
|
+
v = v0 - v1
|
|
584
|
+
n = fast_3x3_cross(u.squeeze(), v.squeeze())
|
|
585
|
+
nn = np.sqrt((n*n).sum())
|
|
586
|
+
self._faces['area'][f_idx] = 0.5*nn
|
|
587
|
+
if (self._faces['halfedge'][f_idx] == -1):
|
|
588
|
+
self._faces['normal'][f_idx] = -1
|
|
589
|
+
if (nn > 0):
|
|
590
|
+
self._faces['normal'][f_idx] = n/nn
|
|
591
|
+
else:
|
|
592
|
+
self._faces['normal'][f_idx] = 0
|
|
593
|
+
|
|
594
|
+
def _update_vertex_neighbors(self, v_idxs):
|
|
595
|
+
"""
|
|
596
|
+
Recalculate len(v_idxs) vertex neighbors/normals.
|
|
597
|
+
|
|
598
|
+
Parameters
|
|
599
|
+
----------
|
|
600
|
+
v_idxs : list or np.array
|
|
601
|
+
List of vertex indicies indicating which vertices to update.
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
if USE_C:
|
|
605
|
+
if isinstance(v_idxs, list):
|
|
606
|
+
v_idxs = np.int32(v_idxs)
|
|
607
|
+
triangle_mesh_utils.c_update_vertex_neighbors(v_idxs, self._halfedges, self._vertices, self._faces)
|
|
608
|
+
else:
|
|
609
|
+
for v_idx in v_idxs:
|
|
610
|
+
_orig = self._vertices['halfedge'][v_idx]
|
|
611
|
+
_curr = _orig
|
|
612
|
+
|
|
613
|
+
curr_edge = self._halfedges[_curr]
|
|
614
|
+
_twin = curr_edge['twin']
|
|
615
|
+
twin_edge = self._halfedges[_twin]
|
|
616
|
+
|
|
617
|
+
if self.debug and (twin_edge['vertex'] != v_idx):
|
|
618
|
+
print(_curr, curr_edge, _twin,twin_edge)
|
|
619
|
+
raise RuntimeError('Twin (%d) should point back to starting vertex (%d) but points to %d' % (_twin, v_idx, twin_edge['vertex']))
|
|
620
|
+
|
|
621
|
+
i = 0
|
|
622
|
+
self._vertices['valence'][v_idx] = 0
|
|
623
|
+
self._vertices['neighbors'][v_idx] = -1
|
|
624
|
+
self._vertices['normal'][v_idx] = 0
|
|
625
|
+
|
|
626
|
+
_normal = 0*self._vertices['normal'][v_idx] # avoid a few lookups by using a local variable
|
|
627
|
+
|
|
628
|
+
while True:
|
|
629
|
+
if (_curr == -1):
|
|
630
|
+
break
|
|
631
|
+
|
|
632
|
+
if (i < NEIGHBORSIZE):
|
|
633
|
+
self._vertices['neighbors'][v_idx, i] = _curr
|
|
634
|
+
|
|
635
|
+
#TODO - should these be in if clause?
|
|
636
|
+
_face = curr_edge['face']
|
|
637
|
+
n = self._faces['normal'][_face]
|
|
638
|
+
a = self._faces['area'][_face]
|
|
639
|
+
_normal += n*a
|
|
640
|
+
else:
|
|
641
|
+
pass
|
|
642
|
+
if self.debug and (i > 2*NEIGHBORSIZE):
|
|
643
|
+
print('Abnormal vertex valance detected on vertex %d' % v_idx)
|
|
644
|
+
# raise RuntimeError('Abnormal vertex valance detected on vertex %d' % v_idx)
|
|
645
|
+
|
|
646
|
+
vertex = self._vertices['position'][v_idx]
|
|
647
|
+
l = vertex - self._vertices['position'][curr_edge['vertex']]
|
|
648
|
+
l = np.sqrt((l*l).sum())
|
|
649
|
+
self._halfedges['length'][_curr] = l
|
|
650
|
+
if (_twin == -1):
|
|
651
|
+
break
|
|
652
|
+
self._halfedges['length'][_twin] = l
|
|
653
|
+
# curr_edge['length'] = l
|
|
654
|
+
# twin_edge['length'] = l
|
|
655
|
+
|
|
656
|
+
_curr = twin_edge['next']
|
|
657
|
+
curr_edge = self._halfedges[_curr]
|
|
658
|
+
_twin = curr_edge['twin']
|
|
659
|
+
twin_edge = self._halfedges[_twin]
|
|
660
|
+
|
|
661
|
+
i += 1
|
|
662
|
+
|
|
663
|
+
if (_curr == _orig):
|
|
664
|
+
break
|
|
665
|
+
|
|
666
|
+
self._vertices['valence'][v_idx] = i
|
|
667
|
+
|
|
668
|
+
if self.debug and (self._vertices['valence'][v_idx] < 3):
|
|
669
|
+
raise RuntimeError('Detected valence <3 on vertex %d' % v_idx)
|
|
670
|
+
|
|
671
|
+
nn = np.sqrt((_normal*_normal).sum())
|
|
672
|
+
if nn > 0:
|
|
673
|
+
self._vertices['normal'][v_idx] = _normal/nn
|
|
674
|
+
else:
|
|
675
|
+
self._vertices['normal'][v_idx] = 0
|
|
676
|
+
|
|
677
|
+
def _resize(self, vec, axis=0, skip_entries=False, return_orig=False, key=None):
|
|
678
|
+
"""
|
|
679
|
+
Increase the size of an input vector 1.5x, keeping all active data.
|
|
680
|
+
|
|
681
|
+
Parameters
|
|
682
|
+
---------
|
|
683
|
+
vec : np.array
|
|
684
|
+
Vector to expand, -1 stored in unused entries.
|
|
685
|
+
axis : int
|
|
686
|
+
Axis (0 or 1) along which to resize vector (only works for 2D
|
|
687
|
+
arrays).
|
|
688
|
+
skip_entries : bool
|
|
689
|
+
Don't copy -1 entries to the new array.
|
|
690
|
+
return_orig : bool
|
|
691
|
+
Return indices of non-negative-one entries in the array prior
|
|
692
|
+
to resize.
|
|
693
|
+
key : string
|
|
694
|
+
Optional array key on which to check for -1 value.
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
if (axis > 1) or (len(vec.shape) > 2):
|
|
698
|
+
raise NotImplementedError('This function only works for 2D arrays along axes 0 and 1.')
|
|
699
|
+
|
|
700
|
+
# Increase the size 1.5x along the axis.
|
|
701
|
+
dt = vec.dtype
|
|
702
|
+
new_size = np.array(vec.shape)
|
|
703
|
+
new_size[axis] = int(1.5*new_size[axis] + 0.5)
|
|
704
|
+
new_size = tuple(new_size)
|
|
705
|
+
|
|
706
|
+
# Allocate memory for new array
|
|
707
|
+
new_vec = np.empty(new_size, dtype=dt)
|
|
708
|
+
new_vec[:] = -1
|
|
709
|
+
|
|
710
|
+
if skip_entries:
|
|
711
|
+
# Check for invalid entries
|
|
712
|
+
if key:
|
|
713
|
+
copy_mask = (vec[key] != 1)
|
|
714
|
+
else:
|
|
715
|
+
copy_mask = (vec != -1)
|
|
716
|
+
|
|
717
|
+
if len(copy_mask.shape) > 1:
|
|
718
|
+
copy_mask = np.any(copy_mask, axis=(1-axis))
|
|
719
|
+
copy_size = np.flatnonzero(copy_mask).size
|
|
720
|
+
else:
|
|
721
|
+
copy_size = np.flatnonzero(copy_mask).size
|
|
722
|
+
|
|
723
|
+
if axis:
|
|
724
|
+
new_vec[:,:copy_size] = vec[:, copy_mask]
|
|
725
|
+
else:
|
|
726
|
+
new_vec[:copy_size] = vec[copy_mask]
|
|
727
|
+
|
|
728
|
+
# Return positions of non-negative-1 entries prior to re-ordering
|
|
729
|
+
if return_orig:
|
|
730
|
+
_orig = np.where(copy_mask)[0]
|
|
731
|
+
# Return empty list if original values are all at the same place
|
|
732
|
+
if np.all(_orig == np.arange(len(_orig))):
|
|
733
|
+
_orig = []
|
|
734
|
+
return new_vec, _orig
|
|
735
|
+
|
|
736
|
+
return new_vec
|
|
737
|
+
else:
|
|
738
|
+
# We're not skipping entries
|
|
739
|
+
if axis:
|
|
740
|
+
copy_size = vec.shape[1]
|
|
741
|
+
new_vec[:,:copy_size] = vec
|
|
742
|
+
else:
|
|
743
|
+
copy_size = vec.shape[0]
|
|
744
|
+
new_vec[:copy_size] = vec
|
|
745
|
+
|
|
746
|
+
if return_orig:
|
|
747
|
+
return new_vec, []
|
|
748
|
+
|
|
749
|
+
# Update array
|
|
750
|
+
return new_vec
|
|
751
|
+
|
|
752
|
+
def edge_collapse(self, _curr, live_update=True):
|
|
753
|
+
"""
|
|
754
|
+
A.k.a. delete two triangles. Remove an edge, defined by halfedge _curr,
|
|
755
|
+
and its associated triangles, while keeping mesh connectivity.
|
|
756
|
+
|
|
757
|
+
Parameters
|
|
758
|
+
----------
|
|
759
|
+
_curr: int
|
|
760
|
+
Pointer to halfedge defining edge to collapse.
|
|
761
|
+
live_update : bool
|
|
762
|
+
Update associated faces and vertices after collapse. Set to
|
|
763
|
+
False to handle this externally (useful if operating on
|
|
764
|
+
multiple, disjoint edges).
|
|
765
|
+
"""
|
|
766
|
+
|
|
767
|
+
if _curr == -1:
|
|
768
|
+
return
|
|
769
|
+
|
|
770
|
+
# Create the pointers we need
|
|
771
|
+
curr_halfedge = self._halfedges[_curr]
|
|
772
|
+
_prev = curr_halfedge['prev']
|
|
773
|
+
_next = curr_halfedge['next']
|
|
774
|
+
_twin = curr_halfedge['twin']
|
|
775
|
+
|
|
776
|
+
if (self._halfedges['twin'][_next] == -1) or (self._halfedges['twin'][_prev] == -1):
|
|
777
|
+
# Collapsing this edge will create another free edge
|
|
778
|
+
return
|
|
779
|
+
|
|
780
|
+
interior = (_twin != -1) # Are we on a boundary?
|
|
781
|
+
|
|
782
|
+
if interior:
|
|
783
|
+
nn = self._vertices['neighbors'][curr_halfedge['vertex']]
|
|
784
|
+
nn_mask = (nn != -1)
|
|
785
|
+
if (-1 in self._halfedges['twin'][nn]*nn_mask):
|
|
786
|
+
return
|
|
787
|
+
|
|
788
|
+
nn = self._vertices['neighbors'][self._halfedges['vertex'][_twin]]
|
|
789
|
+
nn_mask = (nn != -1)
|
|
790
|
+
if (-1 in self._halfedges['twin'][nn]*nn_mask):
|
|
791
|
+
return
|
|
792
|
+
|
|
793
|
+
twin_halfedge = self._halfedges[_twin]
|
|
794
|
+
_twin_prev = twin_halfedge['prev']
|
|
795
|
+
_twin_next = twin_halfedge['next']
|
|
796
|
+
|
|
797
|
+
if self.debug:
|
|
798
|
+
assert(_twin_prev != -1)
|
|
799
|
+
assert(_twin_next != -1)
|
|
800
|
+
|
|
801
|
+
if (self._halfedges['twin'][_twin_prev] == -1) or (self._halfedges['twin'][_twin_next] == -1):
|
|
802
|
+
# Collapsing this edge will create another free edge
|
|
803
|
+
return
|
|
804
|
+
|
|
805
|
+
_dead_vertex = self._halfedges['vertex'][_prev]
|
|
806
|
+
_live_vertex = curr_halfedge['vertex']
|
|
807
|
+
|
|
808
|
+
# Grab the valences of the 4 points near the edge
|
|
809
|
+
if interior:
|
|
810
|
+
vn, vtn = self._vertices['valence'][self._halfedges['vertex'][_next]], self._vertices['valence'][self._halfedges['vertex'][_twin_next]]
|
|
811
|
+
|
|
812
|
+
# Make sure we create no vertices of valence <3 (manifoldness)
|
|
813
|
+
# ((vl + vd - 3) < 4) or
|
|
814
|
+
if (vn < 4) or (vtn < 4):
|
|
815
|
+
return
|
|
816
|
+
|
|
817
|
+
vl, vd = self._vertices['valence'][_live_vertex], self._vertices['valence'][_dead_vertex]
|
|
818
|
+
|
|
819
|
+
if ((vl + vd - 3) < 4):
|
|
820
|
+
return
|
|
821
|
+
|
|
822
|
+
# Check for creation of multivalent edges and prevent this (manifoldness)
|
|
823
|
+
fast_collapse_bool = (self.manifold and (vl < NEIGHBORSIZE) and (vd < NEIGHBORSIZE))
|
|
824
|
+
if fast_collapse_bool:
|
|
825
|
+
# Do it the fast way if we can
|
|
826
|
+
live_nn = self._vertices['neighbors'][_live_vertex]
|
|
827
|
+
dead_nn = self._vertices['neighbors'][_dead_vertex]
|
|
828
|
+
live_mask = (live_nn != -1)
|
|
829
|
+
dead_mask = (dead_nn != -1)
|
|
830
|
+
live_list = self._halfedges['vertex'][live_nn[live_mask]]
|
|
831
|
+
dead_list = self._halfedges['vertex'][dead_nn[dead_mask]]
|
|
832
|
+
else:
|
|
833
|
+
twin_mask = (self._halfedges['twin'] != -1)
|
|
834
|
+
dead_mask = (self._halfedges['vertex'] == _dead_vertex)
|
|
835
|
+
dead_list = self._halfedges['vertex'][self._halfedges['twin'][dead_mask & twin_mask]]
|
|
836
|
+
live_list = self._halfedges['vertex'][self._halfedges['twin'][(self._halfedges['vertex'] == _live_vertex) & twin_mask]]
|
|
837
|
+
|
|
838
|
+
twin_list = list((set(dead_list) & set(live_list)) - set([-1]))
|
|
839
|
+
if len(twin_list) != 2:
|
|
840
|
+
return
|
|
841
|
+
|
|
842
|
+
if self.debug and (_live_vertex == _dead_vertex):
|
|
843
|
+
print(_curr, curr_halfedge, _twin, twin_halfedge)
|
|
844
|
+
raise RuntimeError('Live vertex equals dead vertex (both halves of edge point to same place)')
|
|
845
|
+
|
|
846
|
+
# Collapse to the midpoint of the original edge vertices
|
|
847
|
+
if fast_collapse_bool:
|
|
848
|
+
self._halfedges['vertex'][self._halfedges['twin'][dead_nn[dead_mask]]] = _live_vertex
|
|
849
|
+
else:
|
|
850
|
+
self._halfedges['vertex'][dead_mask] = _live_vertex
|
|
851
|
+
|
|
852
|
+
_live_pos = self._vertices['position'][_live_vertex]
|
|
853
|
+
_dead_pos = self._vertices['position'][_dead_vertex]
|
|
854
|
+
self._vertices['position'][_live_vertex] = 0.5*(_live_pos + _dead_pos)
|
|
855
|
+
|
|
856
|
+
# update valence of vertex we keep
|
|
857
|
+
self._vertices['valence'][_live_vertex] = vl + vd - 3
|
|
858
|
+
|
|
859
|
+
if self.debug:
|
|
860
|
+
print(self._vertices['valence'][_live_vertex], self._vertices['valence'][_dead_vertex])
|
|
861
|
+
assert(self._vertices['valence'][_live_vertex] >=3)
|
|
862
|
+
|
|
863
|
+
# delete dead vertex
|
|
864
|
+
self._vertices[_dead_vertex] = -1
|
|
865
|
+
|
|
866
|
+
# Zipper the remaining triangles
|
|
867
|
+
def _zipper(edge1, edge2):
|
|
868
|
+
# DB optimization notes:
|
|
869
|
+
# 1) there is a (very?) small performance penalty for nested functions - move this up to class level?
|
|
870
|
+
# 2) This would be a good candidate for a c/cython implementation (but possibly applies to whole edge collapse function)
|
|
871
|
+
# 3) function can be simplified (see _zipper1 implementation below). Probably minimal performance impact in
|
|
872
|
+
# python version, but logic flow likely necessary in c/cython (avoids indexing with -1)
|
|
873
|
+
t1 = self._halfedges['twin'][edge1]
|
|
874
|
+
t2 = self._halfedges['twin'][edge2]
|
|
875
|
+
|
|
876
|
+
if edge1 == -1:
|
|
877
|
+
t1 = -1
|
|
878
|
+
if edge2 == -1:
|
|
879
|
+
t2 = -1
|
|
880
|
+
|
|
881
|
+
if (t1 != -1) and (t2 != -1):
|
|
882
|
+
self._halfedges['twin'][t2] = t1
|
|
883
|
+
self._halfedges['twin'][t1] = t2
|
|
884
|
+
else:
|
|
885
|
+
if (t1 != -1):
|
|
886
|
+
self._halfedges['twin'][t1] = -1
|
|
887
|
+
if (t2 != -1):
|
|
888
|
+
self._halfedges['twin'][t2] = -1
|
|
889
|
+
|
|
890
|
+
def _zipper1(edge1, edge2):
|
|
891
|
+
t1 = -1 if edge1 == -1 else self._halfedges['twin'][edge1]
|
|
892
|
+
t2 = -1 if edge2 == -1 else self._halfedges['twin'][edge2]
|
|
893
|
+
|
|
894
|
+
if (t1 != -1):
|
|
895
|
+
self._halfedges['twin'][t1] = t2
|
|
896
|
+
|
|
897
|
+
if (t2 != -1):
|
|
898
|
+
self._halfedges['twin'][t2] = t1
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
_zipper1(_next, _prev)
|
|
902
|
+
if interior:
|
|
903
|
+
_zipper1(_twin_next, _twin_prev)
|
|
904
|
+
|
|
905
|
+
# We need some more pointers
|
|
906
|
+
# TODO: make these safer
|
|
907
|
+
_prev_twin = self._halfedges['twin'][_prev]
|
|
908
|
+
_prev_twin_vertex = self._halfedges['vertex'][_prev_twin]
|
|
909
|
+
_next_prev_twin = self._halfedges['next'][_prev_twin]
|
|
910
|
+
_next_prev_twin_vertex = self._halfedges['vertex'][_next_prev_twin]
|
|
911
|
+
if interior:
|
|
912
|
+
_twin_next_vertex = self._halfedges['vertex'][_twin_next]
|
|
913
|
+
_next_twin_twin_next = self._halfedges['next'][self._halfedges['twin'][_twin_next]]
|
|
914
|
+
_next_twin_twin_next_vertex = self._halfedges['vertex'][_next_twin_twin_next]
|
|
915
|
+
|
|
916
|
+
# Make sure we have good _vertex_halfedges references
|
|
917
|
+
self._vertices['halfedge'][_live_vertex] = _prev_twin
|
|
918
|
+
self._vertices['halfedge'][_prev_twin_vertex] = _next_prev_twin
|
|
919
|
+
if interior:
|
|
920
|
+
self._vertices['halfedge'][_twin_next_vertex] = self._halfedges['twin'][_twin_next]
|
|
921
|
+
self._vertices['halfedge'][_next_twin_twin_next_vertex] = self._halfedges['next'][_next_twin_twin_next]
|
|
922
|
+
|
|
923
|
+
# Grab faces to update
|
|
924
|
+
face0 = self._halfedges['face'][self._halfedges['twin'][_next]]
|
|
925
|
+
face1 = self._halfedges['face'][self._halfedges['twin'][_prev]]
|
|
926
|
+
if interior:
|
|
927
|
+
face2 = self._halfedges['face'][self._halfedges['twin'][_twin_next]]
|
|
928
|
+
face3 = self._halfedges['face'][self._halfedges['twin'][_twin_prev]]
|
|
929
|
+
|
|
930
|
+
# Delete the inner triangles
|
|
931
|
+
self._face_delete(_curr)
|
|
932
|
+
if interior:
|
|
933
|
+
self._face_delete(_twin)
|
|
934
|
+
|
|
935
|
+
if self.debug and interior:
|
|
936
|
+
print(curr_halfedge, twin_halfedge)
|
|
937
|
+
|
|
938
|
+
try:
|
|
939
|
+
if live_update:
|
|
940
|
+
if interior:
|
|
941
|
+
# Update faces
|
|
942
|
+
self._update_face_normals([face0, face1, face2, face3])
|
|
943
|
+
self._update_vertex_neighbors([_live_vertex, _prev_twin_vertex, _next_prev_twin_vertex, _twin_next_vertex])
|
|
944
|
+
else:
|
|
945
|
+
self._update_face_normals([face0, face1])
|
|
946
|
+
self._update_vertex_neighbors([_live_vertex, _prev_twin_vertex, _next_prev_twin_vertex])
|
|
947
|
+
self._faces_by_vertex = None
|
|
948
|
+
|
|
949
|
+
except RuntimeError as e:
|
|
950
|
+
print(_curr, _twin, _next, _prev, _twin_next, _twin_prev, _next_prev_twin, _next_twin_twin_next, _prev_twin)
|
|
951
|
+
print([_live_vertex, _prev_twin_vertex, _next_prev_twin_vertex, _twin_next_vertex])
|
|
952
|
+
raise e
|
|
953
|
+
|
|
954
|
+
def _face_delete(self, _edge):
|
|
955
|
+
"""
|
|
956
|
+
Delete a face defined by the halfedge _edge.
|
|
957
|
+
|
|
958
|
+
Note that this does not account for adjusting halfedges, twins.
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
edge : int
|
|
963
|
+
One self._halfedge index of the edge defining a face to delete.
|
|
964
|
+
"""
|
|
965
|
+
if _edge == -1:
|
|
966
|
+
return
|
|
967
|
+
|
|
968
|
+
curr_edge = self._halfedges[_edge]
|
|
969
|
+
if curr_edge['vertex'] == -1:
|
|
970
|
+
return
|
|
971
|
+
self._faces[curr_edge['face']] = -1
|
|
972
|
+
|
|
973
|
+
self._face_vacancies.append(curr_edge['face'])
|
|
974
|
+
|
|
975
|
+
self._edge_delete(curr_edge['next'])
|
|
976
|
+
self._edge_delete(curr_edge['prev'])
|
|
977
|
+
self._edge_delete(_edge)
|
|
978
|
+
|
|
979
|
+
def _edge_delete(self, _edge):
|
|
980
|
+
"""
|
|
981
|
+
Delete edge _edge in place from self._halfedges.
|
|
982
|
+
|
|
983
|
+
Note that this does not account for adjusting halfedges, twins.
|
|
984
|
+
|
|
985
|
+
Parameters
|
|
986
|
+
----------
|
|
987
|
+
edge : int
|
|
988
|
+
One self._halfedge index of the edge to delete.
|
|
989
|
+
"""
|
|
990
|
+
if (_edge == -1):
|
|
991
|
+
return
|
|
992
|
+
|
|
993
|
+
self._halfedges[_edge] = -1
|
|
994
|
+
self._halfedge_vacancies.append(_edge)
|
|
995
|
+
|
|
996
|
+
self._faces_by_vertex = None
|
|
997
|
+
|
|
998
|
+
def _insert(self, el, el_arr, el_vacancies, key, compact=False, insert_key=None, **kwargs):
|
|
999
|
+
"""
|
|
1000
|
+
Insert an element into an array at the position of the smallest empty
|
|
1001
|
+
entry when searching the array by key.
|
|
1002
|
+
|
|
1003
|
+
Parameters
|
|
1004
|
+
----------
|
|
1005
|
+
el
|
|
1006
|
+
Element to add to the array. Must be one of the dtypes in the
|
|
1007
|
+
structured dtype of el_arr.
|
|
1008
|
+
el_arr : np.array
|
|
1009
|
+
Array where we add the element
|
|
1010
|
+
el_vacancies : list
|
|
1011
|
+
External list where we keep empty positions of el_arr.
|
|
1012
|
+
key : string
|
|
1013
|
+
Key on which to search the array
|
|
1014
|
+
insert_key : string
|
|
1015
|
+
Key on which to insert element el. This is by default the
|
|
1016
|
+
search key.
|
|
1017
|
+
compact : bool
|
|
1018
|
+
Do we copy -1 values in the resize of el_arr?
|
|
1019
|
+
kwargs
|
|
1020
|
+
List of dtype parameters to additionally define el.
|
|
1021
|
+
|
|
1022
|
+
Returns
|
|
1023
|
+
-------
|
|
1024
|
+
ed
|
|
1025
|
+
Element added, includes el stored in full structured dtype of
|
|
1026
|
+
el_arr.
|
|
1027
|
+
idx : int
|
|
1028
|
+
Index of el_arr where ed is stored.
|
|
1029
|
+
el_arr : np.array
|
|
1030
|
+
Modified array containing ed.
|
|
1031
|
+
el_vacancies : list
|
|
1032
|
+
Modified list of el_arr vacancies.
|
|
1033
|
+
"""
|
|
1034
|
+
try:
|
|
1035
|
+
idx = el_vacancies.pop(0)
|
|
1036
|
+
except IndexError:
|
|
1037
|
+
# no vacant slot, resize
|
|
1038
|
+
el_arr = self._resize(el_arr, skip_entries=compact, key=key)
|
|
1039
|
+
#TODO - invalidate neighbours, vertex_halfedges etc ???
|
|
1040
|
+
|
|
1041
|
+
# NOTE: If we search by a different key next time, el_vacancies will
|
|
1042
|
+
# still contain the vacant positions when searching on the previous key.
|
|
1043
|
+
if len(el_arr[key].shape) > 1:
|
|
1044
|
+
# NOTE: If we search on self._vertices['position'] and one position is [-1,-1,-1],
|
|
1045
|
+
# which is not unreasonable, this will replace that vertex. Hence we provide the
|
|
1046
|
+
# option to search and insert on different keys.
|
|
1047
|
+
el_vacancies = [int(x) for x in np.argwhere(np.all(el_arr[key] == -1, axis=1))]
|
|
1048
|
+
else:
|
|
1049
|
+
# el_vacancies = [int(x) for x in np.flatnonzero(el_arr[key] == -1)]
|
|
1050
|
+
el_vacancies = np.flatnonzero(el_arr[key] == -1).tolist()
|
|
1051
|
+
|
|
1052
|
+
idx = el_vacancies.pop(0)
|
|
1053
|
+
|
|
1054
|
+
if idx == -1:
|
|
1055
|
+
raise ValueError('Index cannot be -1.')
|
|
1056
|
+
|
|
1057
|
+
if insert_key is None:
|
|
1058
|
+
# Default to searching and inserting on the same key
|
|
1059
|
+
insert_key = key
|
|
1060
|
+
|
|
1061
|
+
# Put el in el_arr
|
|
1062
|
+
ed = el_arr[idx]
|
|
1063
|
+
ed[insert_key] = el
|
|
1064
|
+
# Define additional structured dtype values for el
|
|
1065
|
+
for k, v in kwargs.items():
|
|
1066
|
+
ed[k] = v
|
|
1067
|
+
|
|
1068
|
+
return ed, idx, el_arr, el_vacancies
|
|
1069
|
+
|
|
1070
|
+
def _new_edge(self, vertex, compact=False, **kwargs):
|
|
1071
|
+
"""
|
|
1072
|
+
Create a new edge.
|
|
1073
|
+
|
|
1074
|
+
Parameters
|
|
1075
|
+
----------
|
|
1076
|
+
vertex : idx
|
|
1077
|
+
self._vertices index which this halfedge points to
|
|
1078
|
+
compact : bool
|
|
1079
|
+
Do we copy -1 values in the resize of _halfedges?
|
|
1080
|
+
kwargs
|
|
1081
|
+
List of _halfedge dtype parameters (e.g. prev=10, next=2) to
|
|
1082
|
+
define halfedge via more than just its vertex.
|
|
1083
|
+
|
|
1084
|
+
Returns
|
|
1085
|
+
-------
|
|
1086
|
+
ed : HALFEDGE_DTYPE
|
|
1087
|
+
Halfedge created
|
|
1088
|
+
idx : int
|
|
1089
|
+
Index of the halfedge in self._halfedges.
|
|
1090
|
+
"""
|
|
1091
|
+
|
|
1092
|
+
ed, idx, self._halfedges, self._halfedge_vacancies = self._insert(vertex, self._halfedges, self._halfedge_vacancies, 'vertex', compact, None, **kwargs)
|
|
1093
|
+
|
|
1094
|
+
return ed, idx
|
|
1095
|
+
|
|
1096
|
+
def _new_face(self, _edge, compact=False, **kwargs):
|
|
1097
|
+
"""
|
|
1098
|
+
Create a new face based on edge _edge.
|
|
1099
|
+
|
|
1100
|
+
Parameters
|
|
1101
|
+
----------
|
|
1102
|
+
_edge : idx
|
|
1103
|
+
self._halfedges index of a single halfedge on this face
|
|
1104
|
+
compact : bool
|
|
1105
|
+
Do we copy -1 values in the resize of _faces?
|
|
1106
|
+
kwargs
|
|
1107
|
+
List of _faces dtype parameters (e.g. area=13.556) to define
|
|
1108
|
+
face via more than just one of its halfedges.
|
|
1109
|
+
|
|
1110
|
+
Returns
|
|
1111
|
+
-------
|
|
1112
|
+
fa : FACE_DTYPE
|
|
1113
|
+
Face created
|
|
1114
|
+
idx : int
|
|
1115
|
+
Index of the face in self._faces.
|
|
1116
|
+
"""
|
|
1117
|
+
|
|
1118
|
+
fa, idx, self._faces, self._face_vacancies = self._insert(_edge, self._faces, self._face_vacancies, 'halfedge', compact, None, **kwargs)
|
|
1119
|
+
|
|
1120
|
+
return fa, idx
|
|
1121
|
+
|
|
1122
|
+
def _new_vertex(self, _vertex, compact=False, **kwargs):
|
|
1123
|
+
"""
|
|
1124
|
+
Insert a new vertex into the mesh.
|
|
1125
|
+
|
|
1126
|
+
Parameters
|
|
1127
|
+
----------
|
|
1128
|
+
_vertex : np.array
|
|
1129
|
+
1D array of x, y, z coordinates of the new vertex.
|
|
1130
|
+
compact : bool
|
|
1131
|
+
Do we copy -1 values in the resize of _vertices?
|
|
1132
|
+
kwargs
|
|
1133
|
+
List of _vertices dtype parameters (e.g. valence=2) to define
|
|
1134
|
+
vertex via more than just its position.
|
|
1135
|
+
|
|
1136
|
+
Returns
|
|
1137
|
+
-------
|
|
1138
|
+
vx : VERTEX_DTYPE
|
|
1139
|
+
Vertex created
|
|
1140
|
+
idx : int
|
|
1141
|
+
Index of the vertex in self._vertices.
|
|
1142
|
+
"""
|
|
1143
|
+
vx, idx, self._vertices, self._vertex_vacancies = self._insert(_vertex, self._vertices, self._vertex_vacancies, 'halfedge', compact, 'position', **kwargs)
|
|
1144
|
+
|
|
1145
|
+
self._faces_by_vertex = None # Reset
|
|
1146
|
+
|
|
1147
|
+
return vx, idx
|
|
1148
|
+
|
|
1149
|
+
def edge_split(self, _curr, live_update=True, upsample=False):
|
|
1150
|
+
"""
|
|
1151
|
+
Split triangles evenly along an edge specified by halfedege index _curr.
|
|
1152
|
+
|
|
1153
|
+
Parameters
|
|
1154
|
+
----------
|
|
1155
|
+
_curr : int
|
|
1156
|
+
Pointer to halfedge defining edge to split.
|
|
1157
|
+
live_update : bool
|
|
1158
|
+
Update associated faces and vertices after split. Set to False
|
|
1159
|
+
to handle this externally (useful if operating on multiple,
|
|
1160
|
+
disjoint edges).
|
|
1161
|
+
upsample: bool
|
|
1162
|
+
Are we doing loop subdivision? If so, keep track of all edges
|
|
1163
|
+
incident on both a new vertex and an old verex that do not
|
|
1164
|
+
split an existing edge.
|
|
1165
|
+
"""
|
|
1166
|
+
if _curr == -1:
|
|
1167
|
+
return
|
|
1168
|
+
|
|
1169
|
+
curr_edge = self._halfedges[_curr]
|
|
1170
|
+
_prev = curr_edge['prev']
|
|
1171
|
+
_next = curr_edge['next']
|
|
1172
|
+
|
|
1173
|
+
# Grab the new vertex position
|
|
1174
|
+
_vertex = 0.5*(self._vertices['position'][curr_edge['vertex'], :] + self._vertices['position'][self._halfedges['vertex'][_prev], :])
|
|
1175
|
+
_, _vertex_idx = self._new_vertex(_vertex)
|
|
1176
|
+
|
|
1177
|
+
_twin = curr_edge['twin']
|
|
1178
|
+
interior = (_twin != -1) # Are we on a boundary?
|
|
1179
|
+
|
|
1180
|
+
if interior:
|
|
1181
|
+
twin_edge = self._halfedges[_twin]
|
|
1182
|
+
_twin_prev = twin_edge['prev']
|
|
1183
|
+
_twin_next = twin_edge['next']
|
|
1184
|
+
|
|
1185
|
+
# Ensure the original faces have the correct pointers and add two new faces
|
|
1186
|
+
self._faces['halfedge'][curr_edge['face']] = _curr
|
|
1187
|
+
if interior:
|
|
1188
|
+
self._faces['halfedge'][twin_edge['face']] = _twin
|
|
1189
|
+
_, _face_1_idx = self._new_face(_twin_prev)
|
|
1190
|
+
self._halfedges['face'][_twin_prev] = _face_1_idx
|
|
1191
|
+
_, _face_2_idx = self._new_face(_next)
|
|
1192
|
+
self._halfedges['face'][_next] = _face_2_idx
|
|
1193
|
+
|
|
1194
|
+
# Insert the new faces
|
|
1195
|
+
_, _he_0_idx = self._new_edge(self._halfedges['vertex'][_next], prev=_curr, next=_prev, face=self._halfedges['face'][_curr])
|
|
1196
|
+
if interior:
|
|
1197
|
+
_, _he_1_idx = self._new_edge(_vertex_idx, prev=_twin_next, next=_twin, face=self._halfedges['face'][_twin])
|
|
1198
|
+
|
|
1199
|
+
_, _he_2_idx = self._new_edge(self._halfedges['vertex'][_twin_next], next=_twin_prev, face=_face_1_idx)
|
|
1200
|
+
_, _he_3_idx = self._new_edge(_vertex_idx, prev=_twin_prev, next=_he_2_idx, face=_face_1_idx)
|
|
1201
|
+
self._halfedges['prev'][_he_2_idx] = _he_3_idx
|
|
1202
|
+
|
|
1203
|
+
_, _he_4_idx = self._new_edge(self._halfedges['vertex'][_curr], next=_next, face=_face_2_idx, twin=-1)
|
|
1204
|
+
_, _he_5_idx = self._new_edge(_vertex_idx, prev=_next, next=_he_4_idx, face=_face_2_idx)
|
|
1205
|
+
self._halfedges['prev'][_he_4_idx] = _he_5_idx
|
|
1206
|
+
|
|
1207
|
+
self._halfedges['twin'][_he_0_idx] = _he_5_idx
|
|
1208
|
+
self._halfedges['twin'][_he_5_idx] = _he_0_idx
|
|
1209
|
+
|
|
1210
|
+
if interior:
|
|
1211
|
+
self._halfedges['twin'][_he_1_idx] = _he_2_idx
|
|
1212
|
+
self._halfedges['twin'][_he_2_idx] = _he_1_idx
|
|
1213
|
+
|
|
1214
|
+
self._halfedges['twin'][_he_3_idx] = _he_4_idx
|
|
1215
|
+
self._halfedges['twin'][_he_4_idx] = _he_3_idx
|
|
1216
|
+
|
|
1217
|
+
# Update _prev, next
|
|
1218
|
+
self._halfedges['prev'][_prev] = _he_0_idx
|
|
1219
|
+
self._halfedges['prev'][_next] = _he_4_idx
|
|
1220
|
+
self._halfedges['next'][_next] = _he_5_idx
|
|
1221
|
+
|
|
1222
|
+
if interior:
|
|
1223
|
+
# Update _twin_next, _twin_prev
|
|
1224
|
+
self._halfedges['next'][_twin_next] = _he_1_idx
|
|
1225
|
+
self._halfedges['prev'][_twin_prev] = _he_2_idx
|
|
1226
|
+
self._halfedges['next'][_twin_prev] = _he_3_idx
|
|
1227
|
+
|
|
1228
|
+
self._halfedges['prev'][_twin] = _he_1_idx
|
|
1229
|
+
# Update _curr and _twin
|
|
1230
|
+
self._halfedges['vertex'][_curr] = _vertex_idx
|
|
1231
|
+
self._halfedges['next'][_curr] = _he_0_idx
|
|
1232
|
+
|
|
1233
|
+
# Update halfedges
|
|
1234
|
+
if interior:
|
|
1235
|
+
self._vertices['halfedge'][self._halfedges['vertex'][_he_2_idx]] = _he_1_idx
|
|
1236
|
+
self._vertices['halfedge'][self._halfedges['vertex'][_prev]] = _curr
|
|
1237
|
+
self._vertices['halfedge'][self._halfedges['vertex'][_he_4_idx]] = _next
|
|
1238
|
+
self._vertices['halfedge'][_vertex_idx] = _he_4_idx
|
|
1239
|
+
self._vertices['halfedge'][self._halfedges['vertex'][_he_0_idx]] = _he_5_idx
|
|
1240
|
+
|
|
1241
|
+
if upsample:
|
|
1242
|
+
# Make sure these edges emanate from the new vertex stored at _vertex_idx
|
|
1243
|
+
if interior:
|
|
1244
|
+
self._loop_subdivision_flip_edges.extend([_he_2_idx])
|
|
1245
|
+
|
|
1246
|
+
self._loop_subdivision_flip_edges.extend([_he_0_idx])
|
|
1247
|
+
self._loop_subdivision_new_vertices.extend([_vertex_idx])
|
|
1248
|
+
|
|
1249
|
+
if live_update:
|
|
1250
|
+
if interior:
|
|
1251
|
+
self._update_face_normals([self._halfedges['face'][_he_0_idx], self._halfedges['face'][_he_1_idx], self._halfedges['face'][_he_2_idx], self._halfedges['face'][_he_4_idx]])
|
|
1252
|
+
self._update_vertex_neighbors([self._halfedges['vertex'][_curr], self._halfedges['vertex'][_twin], self._halfedges['vertex'][_he_0_idx], self._halfedges['vertex'][_he_2_idx], self._halfedges['vertex'][_he_4_idx]])
|
|
1253
|
+
else:
|
|
1254
|
+
self._update_face_normals([self._halfedges['face'][_he_0_idx], self._halfedges['face'][_he_4_idx]])
|
|
1255
|
+
self._update_vertex_neighbors([self._halfedges['vertex'][_curr], self._halfedges['vertex'][_prev], self._halfedges['vertex'][_he_0_idx], self._halfedges['vertex'][_he_4_idx]])
|
|
1256
|
+
self._faces_by_vertex = None
|
|
1257
|
+
|
|
1258
|
+
def edge_flip(self, _curr, live_update=True):
|
|
1259
|
+
"""
|
|
1260
|
+
Flip an edge specified by halfedge index _curr.
|
|
1261
|
+
|
|
1262
|
+
Parameters
|
|
1263
|
+
----------
|
|
1264
|
+
_curr : int
|
|
1265
|
+
Pointer to halfedge defining edge to flip.
|
|
1266
|
+
live_update : bool
|
|
1267
|
+
Update associated faces and vertices after flip. Set to False
|
|
1268
|
+
to handle this externally (useful if operating on multiple,
|
|
1269
|
+
disjoint edges).
|
|
1270
|
+
"""
|
|
1271
|
+
|
|
1272
|
+
if (_curr == -1):
|
|
1273
|
+
return
|
|
1274
|
+
|
|
1275
|
+
curr_edge = self._halfedges[_curr]
|
|
1276
|
+
_twin = curr_edge['twin']
|
|
1277
|
+
if (_twin == -1):
|
|
1278
|
+
# This is a boundary edge
|
|
1279
|
+
return
|
|
1280
|
+
_prev = curr_edge['prev']
|
|
1281
|
+
_next = curr_edge['next']
|
|
1282
|
+
|
|
1283
|
+
twin_edge = self._halfedges[_twin]
|
|
1284
|
+
_twin_prev = twin_edge['prev']
|
|
1285
|
+
_twin_next = twin_edge['next']
|
|
1286
|
+
|
|
1287
|
+
# Make sure both vertices have valence > 3 (preserve manifoldness)
|
|
1288
|
+
vc, vt = self._vertices['valence'][curr_edge['vertex']], self._vertices['valence'][twin_edge['vertex']]
|
|
1289
|
+
if (vc < 4) or (vt < 4):
|
|
1290
|
+
return
|
|
1291
|
+
|
|
1292
|
+
# Calculate adjustments to the halfedges we're flipping
|
|
1293
|
+
new_v0 = self._halfedges['vertex'][_next]
|
|
1294
|
+
new_v1 = self._halfedges['vertex'][_twin_next]
|
|
1295
|
+
|
|
1296
|
+
# If there's already an edge between these two vertices, don't flip (preserve manifoldness)
|
|
1297
|
+
# NOTE: This is potentially a problem if we start with a high-valence mesh. In that case, swap this
|
|
1298
|
+
# check with the more expensive commented one below.
|
|
1299
|
+
|
|
1300
|
+
# Check for creation of multivalent edges and prevent this (manifoldness)
|
|
1301
|
+
fast_collapse_bool = (self.manifold and (vc < NEIGHBORSIZE) and (vt < NEIGHBORSIZE))
|
|
1302
|
+
if fast_collapse_bool:
|
|
1303
|
+
if new_v1 in self._vertices['neighbors'][new_v0]:
|
|
1304
|
+
return
|
|
1305
|
+
else:
|
|
1306
|
+
if new_v1 in self._halfedges['vertex'][self._halfedges['twin'][self._halfedges['vertex'] == new_v0]]:
|
|
1307
|
+
return
|
|
1308
|
+
|
|
1309
|
+
# Convexity check: Let's see if the midpoint of the flipped edge will be above or below the plane of the
|
|
1310
|
+
# current edge
|
|
1311
|
+
flip_midpoint = 0.5*(self._vertices['position'][new_v0] + self._vertices['position'][new_v1])
|
|
1312
|
+
plane_midpoint = (1./3)*(self._vertices['position'][curr_edge['vertex']] + self._vertices['position'][self._halfedges['vertex'][curr_edge['next']]] + self._vertices['position'][self._halfedges['vertex'][curr_edge['prev']]])
|
|
1313
|
+
flipped_dot = ((self._faces['normal'][curr_edge['face']])*(flip_midpoint - plane_midpoint)).sum()
|
|
1314
|
+
|
|
1315
|
+
if flipped_dot < 0:
|
|
1316
|
+
# If flipping moves the midpoint of the edge below the original triangle's plane, this introduces
|
|
1317
|
+
# concavity, so don't flip.
|
|
1318
|
+
return
|
|
1319
|
+
|
|
1320
|
+
# _next's next and prev must be adjusted
|
|
1321
|
+
self._halfedges['prev'][_next] = _twin_prev
|
|
1322
|
+
self._halfedges['next'][_next] = _twin
|
|
1323
|
+
|
|
1324
|
+
# _twin_next's next and prev must be adjusted
|
|
1325
|
+
self._halfedges['prev'][_twin_next] = _prev
|
|
1326
|
+
self._halfedges['next'][_twin_next] = _curr
|
|
1327
|
+
|
|
1328
|
+
# _prev's next and prev must be updated
|
|
1329
|
+
self._halfedges['prev'][_prev] = _curr
|
|
1330
|
+
self._halfedges['next'][_prev] = _twin_next
|
|
1331
|
+
|
|
1332
|
+
# _twin_prev's next and prev must be updated
|
|
1333
|
+
self._halfedges['prev'][_twin_prev] = _twin
|
|
1334
|
+
self._halfedges['next'][_twin_prev] = _next
|
|
1335
|
+
|
|
1336
|
+
# Don't even bother with checking, just make sure to update the
|
|
1337
|
+
# vertex_halfedges references to halfedges we know work
|
|
1338
|
+
self._vertices['halfedge'][curr_edge['vertex']] = _next
|
|
1339
|
+
self._vertices['halfedge'][twin_edge['vertex']] = _twin_next
|
|
1340
|
+
|
|
1341
|
+
# Apply adjustments to the the halfedges we're flipping
|
|
1342
|
+
curr_edge['vertex'] = new_v0
|
|
1343
|
+
twin_edge['vertex'] = new_v1
|
|
1344
|
+
curr_edge['next'] = _prev
|
|
1345
|
+
twin_edge['next'] = _twin_prev
|
|
1346
|
+
curr_edge['prev'] = _twin_next
|
|
1347
|
+
twin_edge['prev'] = _next
|
|
1348
|
+
|
|
1349
|
+
# Update pointers
|
|
1350
|
+
_prev = curr_edge['prev']
|
|
1351
|
+
_next = curr_edge['next']
|
|
1352
|
+
_twin_prev = twin_edge['prev']
|
|
1353
|
+
_twin_next = twin_edge['next']
|
|
1354
|
+
|
|
1355
|
+
# Set faces
|
|
1356
|
+
self._halfedges['face'][_next] = curr_edge['face']
|
|
1357
|
+
self._halfedges['face'][_prev] = curr_edge['face']
|
|
1358
|
+
self._halfedges['face'][_twin_next] = twin_edge['face']
|
|
1359
|
+
self._halfedges['face'][_twin_prev] = twin_edge['face']
|
|
1360
|
+
|
|
1361
|
+
# Finish updating vertex_halfedges references, update face references
|
|
1362
|
+
self._vertices['halfedge'][curr_edge['vertex']] = _twin
|
|
1363
|
+
self._vertices['halfedge'][twin_edge['vertex']] = _curr
|
|
1364
|
+
self._faces['halfedge'][curr_edge['face']] = _curr
|
|
1365
|
+
self._faces['halfedge'][twin_edge['face']] = _twin
|
|
1366
|
+
|
|
1367
|
+
if live_update:
|
|
1368
|
+
# Update face and vertex normals
|
|
1369
|
+
self._update_face_normals([curr_edge['face'], twin_edge['face']])
|
|
1370
|
+
self._update_vertex_neighbors([curr_edge['vertex'], twin_edge['vertex'], self._halfedges['vertex'][_next], self._halfedges['vertex'][_twin_next]])
|
|
1371
|
+
|
|
1372
|
+
self._faces_by_vertex = None
|
|
1373
|
+
|
|
1374
|
+
def regularize(self):
|
|
1375
|
+
"""
|
|
1376
|
+
Adjust vertices so they tend toward valence VALENCE
|
|
1377
|
+
(or BOUNDARY_VALENCE for boundaries).
|
|
1378
|
+
"""
|
|
1379
|
+
flip_count = 0
|
|
1380
|
+
|
|
1381
|
+
# Do a single pass over all active edges and flip them if the flip minimizes the deviation of vertex valences
|
|
1382
|
+
# for i in np.arange(len(self._halfedges['vertex'])):
|
|
1383
|
+
# if (self._halfedges['vertex'][i] < 0):
|
|
1384
|
+
# continue
|
|
1385
|
+
for i in np.flatnonzero(self._halfedges['vertex'] != -1):
|
|
1386
|
+
|
|
1387
|
+
curr_edge = self._halfedges[i]
|
|
1388
|
+
_twin = curr_edge['twin']
|
|
1389
|
+
twin_edge = self._halfedges[_twin]
|
|
1390
|
+
|
|
1391
|
+
target_valence = VALENCE
|
|
1392
|
+
if _twin == -1:
|
|
1393
|
+
# boundary
|
|
1394
|
+
target_valence = BOUNDARY_VALENCE
|
|
1395
|
+
|
|
1396
|
+
# Pre-flip vertices
|
|
1397
|
+
v1 = self._vertices['valence'][curr_edge['vertex']] - target_valence
|
|
1398
|
+
v2 = self._vertices['valence'][twin_edge['vertex']] - target_valence
|
|
1399
|
+
|
|
1400
|
+
# Post-flip vertices
|
|
1401
|
+
v3 = self._vertices['valence'][self._halfedges['vertex'][curr_edge['next']]] - target_valence
|
|
1402
|
+
v4 = self._vertices['valence'][self._halfedges['vertex'][twin_edge['next']]] - target_valence
|
|
1403
|
+
|
|
1404
|
+
# Check valence deviation from VALENCE (or
|
|
1405
|
+
# BOUNDARY_VALENCE for boundaries) pre- and post-flip
|
|
1406
|
+
score_pre = np.abs([v1,v2,v3,v4]).sum()
|
|
1407
|
+
score_post = np.abs([v1-1,v2-1,v3+1,v4+1]).sum()
|
|
1408
|
+
|
|
1409
|
+
if score_post < score_pre:
|
|
1410
|
+
# Flip minimizes deviation of vertex valences from VALENCE (or
|
|
1411
|
+
# BOUNDARY_VALENCE for boundaries)
|
|
1412
|
+
self.edge_flip(i)
|
|
1413
|
+
flip_count += 1
|
|
1414
|
+
|
|
1415
|
+
print('Flip count: %d' % (flip_count))
|
|
1416
|
+
|
|
1417
|
+
def relax(self, l=1, n=1):
|
|
1418
|
+
"""
|
|
1419
|
+
Perform n iterations of Lloyd relaxation on the mesh.
|
|
1420
|
+
|
|
1421
|
+
Parameters
|
|
1422
|
+
----------
|
|
1423
|
+
l : float
|
|
1424
|
+
Regularization (damping) term, used to avoid oscillations.
|
|
1425
|
+
n : int
|
|
1426
|
+
Number of iterations to apply.
|
|
1427
|
+
"""
|
|
1428
|
+
|
|
1429
|
+
for k in range(n):
|
|
1430
|
+
# Get vertex neighbors
|
|
1431
|
+
nn = self._vertices['neighbors']
|
|
1432
|
+
nn_mask = (self._vertices['neighbors'] != -1)
|
|
1433
|
+
vn = self._vertices['position'][self._halfedges['vertex'][nn]]
|
|
1434
|
+
|
|
1435
|
+
# Weight by distance to neighors
|
|
1436
|
+
an = (1./self._halfedges['length'][nn])*nn_mask
|
|
1437
|
+
an[self._halfedges['length'][nn] == 0] = 0
|
|
1438
|
+
|
|
1439
|
+
# Calculate gravity-weighted centroids
|
|
1440
|
+
A = np.sum(an, axis=1)
|
|
1441
|
+
c = 1./(A[...,None])*np.sum(an[...,None]*vn, axis=1)
|
|
1442
|
+
# Don't get messed up by slivers. NOTE: This is a hack in case
|
|
1443
|
+
# edge split/collapse don't do their jobs.
|
|
1444
|
+
A_mask = (A == 0)
|
|
1445
|
+
c[A_mask] = self._vertices['position'][A_mask]
|
|
1446
|
+
if self.fix_boundary:
|
|
1447
|
+
# Don't move vertices on a boundary
|
|
1448
|
+
boundary = self._halfedges[(self._halfedges['twin'] == -1)]
|
|
1449
|
+
tn = np.hstack([boundary['vertex'], self._halfedges['vertex'][boundary['prev']]])
|
|
1450
|
+
c[tn] = self._vertices['position'][tn]
|
|
1451
|
+
|
|
1452
|
+
# Construct projection vector into tangent plane
|
|
1453
|
+
pn = self._vertices['normal'][...,None]*self._vertices['normal'][:,None,:]
|
|
1454
|
+
p = np.eye(3)[None,:] - pn
|
|
1455
|
+
|
|
1456
|
+
# Update vertex positions
|
|
1457
|
+
self._vertices['position'] = self._vertices['position'] + l*np.sum(p*((c-self._vertices['position'])[...,None]),axis=1)
|
|
1458
|
+
|
|
1459
|
+
# Now we gotta recalculate the normals
|
|
1460
|
+
self._faces['normal'][:] = -1
|
|
1461
|
+
self._vertices['normal'][:] = -1
|
|
1462
|
+
self.face_normals
|
|
1463
|
+
self.vertex_normals
|
|
1464
|
+
|
|
1465
|
+
def remesh(self, n=5, target_edge_length=-1, l=0.5, n_relax=10):
|
|
1466
|
+
"""
|
|
1467
|
+
Produce a higher-quality mesh.
|
|
1468
|
+
|
|
1469
|
+
Follows procedure in Botsch and Kobbelt, A Remeshing Approach to
|
|
1470
|
+
Multiresoluton Modeling, Eurographics Symposium on Geometry Processing,
|
|
1471
|
+
2004.
|
|
1472
|
+
|
|
1473
|
+
Parameters
|
|
1474
|
+
----------
|
|
1475
|
+
n : int
|
|
1476
|
+
Number of remeshing iterations to apply.
|
|
1477
|
+
target_edge_length : float
|
|
1478
|
+
Target edge length for all edges in the mesh.
|
|
1479
|
+
l : float
|
|
1480
|
+
Relaxation regularization term, used to avoid oscillations.
|
|
1481
|
+
n_relax : int
|
|
1482
|
+
Number of Lloyd relaxation (relax()) iterations to apply
|
|
1483
|
+
per remeshing iteration.
|
|
1484
|
+
"""
|
|
1485
|
+
|
|
1486
|
+
if (target_edge_length == -1):
|
|
1487
|
+
# Guess edge_length
|
|
1488
|
+
target_edge_length = np.mean(self._halfedges['length'][self._halfedges['length'] != -1])
|
|
1489
|
+
|
|
1490
|
+
for k in range(n):
|
|
1491
|
+
# 1. Split all edges longer than (4/3)*target_edge_length at their midpoint.
|
|
1492
|
+
split_count = 0
|
|
1493
|
+
for i in np.arange(len(self._halfedges['length'])):
|
|
1494
|
+
if (self._halfedges['vertex'][i] != -1) and (self._halfedges['length'][i] > 1.33*target_edge_length):
|
|
1495
|
+
self.edge_split(i)
|
|
1496
|
+
split_count += 1
|
|
1497
|
+
print('Split count: %d' % (split_count))
|
|
1498
|
+
|
|
1499
|
+
if self.debug:
|
|
1500
|
+
# check for dud half edges
|
|
1501
|
+
_valid = self._halfedges['vertex'] != -1
|
|
1502
|
+
assert(not np.any(self._halfedges['twin'][_valid] == -1))
|
|
1503
|
+
assert (not np.any(self._halfedges['next'][_valid] == -1))
|
|
1504
|
+
assert (not np.any(self._halfedges['vertex'][self._halfedges['next'][_valid]] == -1))
|
|
1505
|
+
|
|
1506
|
+
assert (not np.any(self._halfedges['next'][self._halfedges['twin'][_valid]] == self._halfedges['twin'][self._halfedges['prev'][_valid]]))
|
|
1507
|
+
|
|
1508
|
+
assert(self._vertices['valence'][self._vertices['valence']>0].min() >=3)
|
|
1509
|
+
|
|
1510
|
+
# 2. Collapse all edges shorter than (4/5)*target_edge_length to their midpoint.
|
|
1511
|
+
collapse_count = 0
|
|
1512
|
+
for i in np.arange(len(self._halfedges['length'])):
|
|
1513
|
+
if (self._halfedges['vertex'][i] != -1) and (self._halfedges['length'][i] < 0.8*target_edge_length):
|
|
1514
|
+
self.edge_collapse(i)
|
|
1515
|
+
collapse_count += 1
|
|
1516
|
+
print('Collapse count: ' + str(collapse_count))
|
|
1517
|
+
|
|
1518
|
+
# 3. Flip edges in order to minimize deviation from VALENCE.
|
|
1519
|
+
self.regularize()
|
|
1520
|
+
|
|
1521
|
+
# 4. Relocate vertices on the surface by tangential smoothing.
|
|
1522
|
+
self.relax(l=l, n=n_relax)
|
|
1523
|
+
|
|
1524
|
+
# Let's double-check the mesh manifoldness
|
|
1525
|
+
self._manifold = None
|
|
1526
|
+
self.manifold
|
|
1527
|
+
|
|
1528
|
+
def upsample(self, n=1):
|
|
1529
|
+
"""
|
|
1530
|
+
Upsample the mesh by a factor of 4 (factor of 2 along each axis in the
|
|
1531
|
+
plane of the mesh) and smooth meshes by Loop's 5/8 and 3/8 rule.
|
|
1532
|
+
|
|
1533
|
+
References:
|
|
1534
|
+
1. C. T. Loop, "Smooth Subdivision surfaces Based on Triangles,"
|
|
1535
|
+
University of Utah, 1987
|
|
1536
|
+
2. http://462cmu.github.io/asst2_meshedit/, task 4
|
|
1537
|
+
|
|
1538
|
+
Parameters
|
|
1539
|
+
----------
|
|
1540
|
+
n : int
|
|
1541
|
+
Number of upsampling operations to perform.
|
|
1542
|
+
"""
|
|
1543
|
+
|
|
1544
|
+
for k in range(n):
|
|
1545
|
+
# Reset
|
|
1546
|
+
split_edges = {}
|
|
1547
|
+
self._loop_subdivision_new_vertices = []
|
|
1548
|
+
self._loop_subdivision_flip_edges = []
|
|
1549
|
+
new_vertex_idxs = []
|
|
1550
|
+
|
|
1551
|
+
# Mark the current edges as old
|
|
1552
|
+
edges_to_split = np.where(self._halfedges['length'] != -1)[0]
|
|
1553
|
+
split_halfedges = self._halfedges[edges_to_split]
|
|
1554
|
+
|
|
1555
|
+
# Compute new vertex positions for old vertices
|
|
1556
|
+
old_vertex_idxs = split_halfedges['vertex']
|
|
1557
|
+
old_vertices = self._vertices[old_vertex_idxs]
|
|
1558
|
+
old_neighbors = old_vertices['neighbors']
|
|
1559
|
+
neg_mask = (old_neighbors != -1)
|
|
1560
|
+
# alpha is a function of vertex order N that satisifies constraints on equation 3.4 of Loop's thesis
|
|
1561
|
+
alpha = np.tanh(LOOP_ALPHA_FACTOR*old_vertices['valence'])
|
|
1562
|
+
old_vertex_positions = (alpha[:,None])*old_vertices['position'] + ((1-alpha)[:,None])*((1./old_vertices['valence'])[:,None])*np.sum(self._vertices['position'][self._halfedges['vertex'][old_neighbors]]*neg_mask[...,None],axis=1)
|
|
1563
|
+
|
|
1564
|
+
# Compute new vertex positions for new vertices
|
|
1565
|
+
p0 = 0.375*(old_vertices['position'] + self._vertices['position'][self._halfedges['vertex'][split_halfedges['twin']]])
|
|
1566
|
+
p1 = 0.125*(self._vertices['position'][self._halfedges['vertex'][split_halfedges['next']]] + self._vertices['position'][self._halfedges['vertex'][self._halfedges['next'][split_halfedges['twin']]]])
|
|
1567
|
+
new_vertex_positions = p0 + p1
|
|
1568
|
+
|
|
1569
|
+
# 1. Split every edge in the mesh
|
|
1570
|
+
for j, i in enumerate(edges_to_split):
|
|
1571
|
+
if i in split_edges:
|
|
1572
|
+
continue
|
|
1573
|
+
|
|
1574
|
+
split_edges[self._halfedges['twin'][i]] = i
|
|
1575
|
+
new_vertex_idxs.append(j)
|
|
1576
|
+
self.edge_split(i, upsample=True)
|
|
1577
|
+
|
|
1578
|
+
# 2. Flip any new edge that touches an old vertex and a new vertex
|
|
1579
|
+
edges_to_flip = list(set(self._halfedges['vertex'][self._loop_subdivision_flip_edges]) - set(self._loop_subdivision_new_vertices))
|
|
1580
|
+
for e in edges_to_flip:
|
|
1581
|
+
self.edge_flip(e)
|
|
1582
|
+
|
|
1583
|
+
# Get any boundary vertices
|
|
1584
|
+
boundary_vertex_idxs = self._halfedges['vertex'][(self._halfedges['twin'] == -1)]
|
|
1585
|
+
boundary_vertex_positions = self._vertices['position'][boundary_vertex_idxs]
|
|
1586
|
+
|
|
1587
|
+
# Copy new position values into self._vertices['position']
|
|
1588
|
+
self._vertices['position'][old_vertex_idxs] = old_vertex_positions
|
|
1589
|
+
self._vertices['position'][self._loop_subdivision_new_vertices] = new_vertex_positions[new_vertex_idxs]
|
|
1590
|
+
|
|
1591
|
+
# Restore boundary vertex positions
|
|
1592
|
+
self._vertices['position'][boundary_vertex_idxs] = boundary_vertex_positions
|
|
1593
|
+
|
|
1594
|
+
self._manifold = None
|
|
1595
|
+
|
|
1596
|
+
def downsample(self, n_triangles=None):
|
|
1597
|
+
"""
|
|
1598
|
+
Mesh downsampling via quadratic error metrics.
|
|
1599
|
+
|
|
1600
|
+
Follows procedure in Garland and Heckbert, Surface Simplification Using
|
|
1601
|
+
Quadtratic Error Metrics.
|
|
1602
|
+
|
|
1603
|
+
Parameters
|
|
1604
|
+
----------
|
|
1605
|
+
n_triangles : int
|
|
1606
|
+
Target number of triangles in the downsampled mesh.
|
|
1607
|
+
"""
|
|
1608
|
+
|
|
1609
|
+
import PYME.experimental._treap as treap
|
|
1610
|
+
|
|
1611
|
+
# 1. Compute quadratic matrices for all initial vertices
|
|
1612
|
+
v = self._vertices['position']
|
|
1613
|
+
n = self._vertices['normal']
|
|
1614
|
+
d = (-n*v).sum(1)
|
|
1615
|
+
p = np.vstack([n.T, d]).T # plane
|
|
1616
|
+
vn = self._vertices['neighbors']
|
|
1617
|
+
vn_mask = (vn != -1)
|
|
1618
|
+
pp = p[self._halfedges['vertex'][vn]]*vn_mask[...,None] # neighbor planes
|
|
1619
|
+
Q = (pp[...,None]*pp[:,:,None,:]).sum(1) # per-vertex quadratic
|
|
1620
|
+
|
|
1621
|
+
# 2. Select all valid pairs
|
|
1622
|
+
# For now, operate on edges. In the future we can also operate on any
|
|
1623
|
+
# pair of vertex positions such that || v_1 - v_2 || < t, where v_1,
|
|
1624
|
+
# v_2 \in R^3, and t \in R is a threshold.
|
|
1625
|
+
|
|
1626
|
+
# 3. Compute optimal contraction target and cost of each contraction
|
|
1627
|
+
# NOTE: we do this for all halfedges, empty or not. This saves us from having to search for
|
|
1628
|
+
# unique halfedges later in step 5. Instead, we just ignore -1 values.
|
|
1629
|
+
edges_mask = (self._halfedges['vertex'] != -1)
|
|
1630
|
+
edges = np.vstack([self._halfedges['vertex'], self._halfedges['vertex'][self._halfedges['prev']]]).T
|
|
1631
|
+
# Mask and shift to avoid a copy operation on Q
|
|
1632
|
+
Q_mask = np.ones((4,4))
|
|
1633
|
+
Q_mask[3,:] = 0
|
|
1634
|
+
Q_shift = np.zeros((4,4))
|
|
1635
|
+
Q_shift[3,3] = 1
|
|
1636
|
+
# h is the solution vector for least squares
|
|
1637
|
+
h = np.array([0,0,0,1])
|
|
1638
|
+
# 3a. Compute the optimal contraction target for each valid pair
|
|
1639
|
+
Q_edge = (Q[edges]).sum(1) # Q paired
|
|
1640
|
+
try:
|
|
1641
|
+
vp = (np.linalg.inv(Q_edge*Q_mask[None,...] + Q_shift[None,...])*h).sum(2)
|
|
1642
|
+
except(np.linalg.LinAlgError):
|
|
1643
|
+
vp = 0.5*np.sum(self._vertices['position'][edges],axis=1)
|
|
1644
|
+
vp = np.vstack([vp.T, np.ones(vp.shape[0])]).T
|
|
1645
|
+
|
|
1646
|
+
# 3b. Compute cost of contractions
|
|
1647
|
+
err = ((vp[:,None,:]*Q_edge).sum(2)*vp).sum(1)
|
|
1648
|
+
|
|
1649
|
+
# 4. Place all the pairs in a heap keyed on cost with the minimum cost pair on top
|
|
1650
|
+
cost_heap = treap.Treap()
|
|
1651
|
+
for i in (np.arange(len(edges))[edges_mask]):
|
|
1652
|
+
cost_heap.insert(np.float32(err[i]), np.int32(i))
|
|
1653
|
+
|
|
1654
|
+
# 5. Iteratively remove the pair of least cost from the heap,
|
|
1655
|
+
# contract this pair, and update the costs of all valid
|
|
1656
|
+
# pairs involving the remaining live vertex.
|
|
1657
|
+
n_faces = np.sum(self._faces['halfedge'] != -1)
|
|
1658
|
+
if n_triangles is None:
|
|
1659
|
+
# Assume we want to downsample by a factor of 4
|
|
1660
|
+
n_triangles = int(0.25*n_faces)
|
|
1661
|
+
collapse_tries = int(0.125*(n_faces - n_triangles))
|
|
1662
|
+
while True:
|
|
1663
|
+
if n_faces <= n_triangles:
|
|
1664
|
+
# Sometimes edge_collapse does not collapse the edge
|
|
1665
|
+
# because it would affect the manifoldness of the mesh,
|
|
1666
|
+
# so we need to see how far off we are from the desired
|
|
1667
|
+
# number of triangles n_triangles.
|
|
1668
|
+
tmp = np.sum(self._faces['halfedge'] != -1)
|
|
1669
|
+
if (n_faces == tmp) or (collapse_tries == 0):
|
|
1670
|
+
# We don't want to double check ourselves forever
|
|
1671
|
+
break
|
|
1672
|
+
# Update our estimate and keep refining the mesh
|
|
1673
|
+
n_faces = tmp
|
|
1674
|
+
collapse_tries -= 1
|
|
1675
|
+
|
|
1676
|
+
# Grab the edge with minimum cost
|
|
1677
|
+
_, _edge = cost_heap.pop()
|
|
1678
|
+
|
|
1679
|
+
if _edge == -1:
|
|
1680
|
+
print('Edge was -1???')
|
|
1681
|
+
continue
|
|
1682
|
+
|
|
1683
|
+
# Compute the new quadratic
|
|
1684
|
+
edge = self._halfedges[_edge]
|
|
1685
|
+
# Grab the live and dead vertex
|
|
1686
|
+
_live_vertex = edge['vertex']
|
|
1687
|
+
_dead_vertex = self._halfedges['vertex'][edge['prev']]
|
|
1688
|
+
Q_edge[_edge] = Q[_live_vertex] + Q[_dead_vertex]
|
|
1689
|
+
|
|
1690
|
+
if (_live_vertex == -1) or (_dead_vertex == -1):
|
|
1691
|
+
print('Vertex was -1???')
|
|
1692
|
+
continue
|
|
1693
|
+
|
|
1694
|
+
# Grab all neighboring halfedges
|
|
1695
|
+
nn_live = self._vertices['neighbors'][_live_vertex]
|
|
1696
|
+
nn_live_mask = (nn_live != -1)
|
|
1697
|
+
nn_live_twin = self._halfedges['twin'][nn_live[nn_live_mask]]
|
|
1698
|
+
nn_live_twin_mask = (nn_live_twin != -1)
|
|
1699
|
+
nn_dead = self._vertices['neighbors'][_dead_vertex]
|
|
1700
|
+
nn_dead_mask = (nn_dead != -1)
|
|
1701
|
+
nn_dead_twin = self._halfedges['twin'][nn_dead[nn_dead_mask]]
|
|
1702
|
+
nn_dead_twin_mask = (nn_dead_twin != -1)
|
|
1703
|
+
neighbors = np.hstack([nn_live[nn_live_mask], nn_live_twin[nn_live_twin_mask], nn_dead[nn_dead_mask], nn_dead_twin[nn_dead_twin_mask]])
|
|
1704
|
+
|
|
1705
|
+
# Eliminate edges touching either vertex from the cost heap
|
|
1706
|
+
for neighbor in neighbors:
|
|
1707
|
+
cost_heap.delete(np.int32(neighbor))
|
|
1708
|
+
|
|
1709
|
+
# Collapse the edge
|
|
1710
|
+
self.edge_collapse(_edge)
|
|
1711
|
+
|
|
1712
|
+
# Decrement the estimate of the number of triangles in the
|
|
1713
|
+
# mesh accordingly
|
|
1714
|
+
n_faces -= 2
|
|
1715
|
+
|
|
1716
|
+
# Set the quadratic of _live_vertex to the edge quadratic
|
|
1717
|
+
Q[_live_vertex] = Q_edge[_edge]
|
|
1718
|
+
# self._vertices['position'][_live_vertex] = vp[_edge,:3]
|
|
1719
|
+
|
|
1720
|
+
# Grab the new neighbors
|
|
1721
|
+
nn_live = self._vertices['neighbors'][_live_vertex]
|
|
1722
|
+
nn_live_mask = (nn_live != -1)
|
|
1723
|
+
nn_live_twin = self._halfedges['twin'][nn_live[nn_live_mask]]
|
|
1724
|
+
nn_live_twin_mask = (nn_live_twin != -1)
|
|
1725
|
+
neighbors = np.hstack([nn_live[nn_live_mask], nn_live_twin[nn_live_twin_mask]])
|
|
1726
|
+
|
|
1727
|
+
# Update the costs and positions of each of these neighbors and re-insert them into the queue
|
|
1728
|
+
new_edges = np.vstack([self._halfedges['vertex'][neighbors], self._halfedges['vertex'][self._halfedges['prev'][neighbors]]]).T
|
|
1729
|
+
Q_edge[neighbors] = (Q[new_edges]).sum(1)
|
|
1730
|
+
|
|
1731
|
+
try:
|
|
1732
|
+
vp[neighbors] = (np.linalg.inv(Q_edge[neighbors]*Q_mask[None,...] + Q_shift[None,...])*h).sum(2)
|
|
1733
|
+
except(np.linalg.LinAlgError):
|
|
1734
|
+
vp_temp = 0.5*np.sum(self._vertices['position'][new_edges],axis=1)
|
|
1735
|
+
vp[neighbors] = np.vstack([vp_temp.T, np.ones(vp_temp.shape[0])]).T
|
|
1736
|
+
|
|
1737
|
+
err[neighbors] = ((vp[neighbors,None,:]*Q_edge[neighbors]).sum(2)*vp[neighbors]).sum(1)
|
|
1738
|
+
|
|
1739
|
+
for n in neighbors:
|
|
1740
|
+
cost_heap.insert(np.float32(err[n]), np.int32(n))
|
|
1741
|
+
|
|
1742
|
+
self._manifold = None
|
|
1743
|
+
|
|
1744
|
+
def find_connected_components(self):
|
|
1745
|
+
"""
|
|
1746
|
+
Label connected components of the mesh.
|
|
1747
|
+
"""
|
|
1748
|
+
|
|
1749
|
+
# Set all components to -1 (background)
|
|
1750
|
+
self._halfedges['component'] = -1
|
|
1751
|
+
self._vertices['component'] = -1
|
|
1752
|
+
self._faces['component'] = -1
|
|
1753
|
+
|
|
1754
|
+
# Connect component by vertex
|
|
1755
|
+
component = 0
|
|
1756
|
+
for _vertex in np.arange(self._vertices.shape[0]):
|
|
1757
|
+
curr_vertex = self._vertices[_vertex]
|
|
1758
|
+
if (curr_vertex['halfedge'] == -1) or (curr_vertex['component'] != -1):
|
|
1759
|
+
# We only want to deal with valid, unassigned vertices
|
|
1760
|
+
continue
|
|
1761
|
+
|
|
1762
|
+
# Search vertices contains vertices connected to this vertex.
|
|
1763
|
+
# We want to see if there are other vertices connected to the
|
|
1764
|
+
# search vertices.
|
|
1765
|
+
search_vertices = []
|
|
1766
|
+
self._vertices[_vertex]['component'] = component
|
|
1767
|
+
search_vertices.append(_vertex)
|
|
1768
|
+
|
|
1769
|
+
# Find vertices connected to the search vertices
|
|
1770
|
+
while search_vertices:
|
|
1771
|
+
# Grab the latest search vertex neighbors
|
|
1772
|
+
_v = search_vertices.pop()
|
|
1773
|
+
nn = self._vertices['neighbors'][_v]
|
|
1774
|
+
# Loop over the neighbors
|
|
1775
|
+
for _edge in nn:
|
|
1776
|
+
if _edge == -1:
|
|
1777
|
+
continue
|
|
1778
|
+
# Assign the vertex component label to halfedges and faces
|
|
1779
|
+
# emanating from this vertex
|
|
1780
|
+
curr_edge = self._halfedges[_edge]
|
|
1781
|
+
self._halfedges['component'][_edge] = component
|
|
1782
|
+
self._halfedges['component'][curr_edge['prev']] = component
|
|
1783
|
+
self._halfedges['component'][curr_edge['next']] = component
|
|
1784
|
+
self._faces['component'][curr_edge['face']] = component
|
|
1785
|
+
|
|
1786
|
+
# If the vertex is assigned, we've already visited it and
|
|
1787
|
+
# don't need to do so again.
|
|
1788
|
+
if self._vertices['component'][curr_edge['vertex']] != -1:
|
|
1789
|
+
continue
|
|
1790
|
+
self._vertices['component'][curr_edge['vertex']] = component
|
|
1791
|
+
search_vertices.append(curr_edge['vertex'])
|
|
1792
|
+
|
|
1793
|
+
# Increment the label as any vertices not included in this
|
|
1794
|
+
# iteration's search will be part of another component.
|
|
1795
|
+
component += 1
|
|
1796
|
+
|
|
1797
|
+
def keep_largest_connected_component(self):
|
|
1798
|
+
# Find the connected components
|
|
1799
|
+
self.find_connected_components()
|
|
1800
|
+
|
|
1801
|
+
# Which connected component is largest?
|
|
1802
|
+
com, counts = np.unique(self._vertices['component'][self._vertices['component']!=-1], return_counts=True)
|
|
1803
|
+
max_count = np.argmax(counts)
|
|
1804
|
+
max_com = com[max_count]
|
|
1805
|
+
|
|
1806
|
+
# Remove the smaller components
|
|
1807
|
+
_vertices = np.where((self._vertices['component'] != max_com))[0]
|
|
1808
|
+
_edges = np.where((self._halfedges['component'] != max_com))[0]
|
|
1809
|
+
_edges_with_twins = _edges[self._halfedges['twin'][_edges] != -1]
|
|
1810
|
+
_faces = np.where((self._faces['component'] != max_com))[0]
|
|
1811
|
+
self._vertices[_vertices] = -1
|
|
1812
|
+
_kept_edges = self._halfedges['twin'][_edges_with_twins]
|
|
1813
|
+
self._halfedges['twin'][_kept_edges] = -1
|
|
1814
|
+
self._halfedges[_edges] = -1
|
|
1815
|
+
self._faces[_faces] = -1
|
|
1816
|
+
|
|
1817
|
+
# Mark deleted faces as available
|
|
1818
|
+
self._vertex_vacancies = list(set(self._vertex_vacancies).union(set(_vertices)))
|
|
1819
|
+
self._halfedge_vacancies = list(set(self._halfedge_vacancies).union(set(_edges)))
|
|
1820
|
+
self._face_vacancies = list(set(self._face_vacancies).union(set(_faces)))
|
|
1821
|
+
|
|
1822
|
+
# Update newly orphaned edges
|
|
1823
|
+
self._update_face_normals(list(set(self._halfedges['face'][_kept_edges])))
|
|
1824
|
+
self._update_vertex_neighbors(list(set(self._halfedges['vertex'][_kept_edges])))
|
|
1825
|
+
|
|
1826
|
+
self._faces_by_vertex = None
|
|
1827
|
+
|
|
1828
|
+
def _find_boundary_polygons(self):
|
|
1829
|
+
"""
|
|
1830
|
+
Return a list of closed polygons, each defined by a list of halfedges,
|
|
1831
|
+
where each polygon defines a boundary in the mesh.
|
|
1832
|
+
"""
|
|
1833
|
+
boundary_polygons = []
|
|
1834
|
+
|
|
1835
|
+
# 1. Construct an initial list of boundary edges
|
|
1836
|
+
boundary_edges = list(np.where((self._halfedges['twin'] == -1) & (self._halfedges['vertex'] != -1))[0])
|
|
1837
|
+
|
|
1838
|
+
# 2. Get the boundary polygons
|
|
1839
|
+
while len(boundary_edges) > 0:
|
|
1840
|
+
_root = boundary_edges[-1] # initial halfedge defining the polygon
|
|
1841
|
+
curr_polygon = [_root]
|
|
1842
|
+
# Step next-twin-next to move around the boundary
|
|
1843
|
+
_next = self._halfedges['next'][_root]
|
|
1844
|
+
_twin = self._halfedges['twin'][_next]
|
|
1845
|
+
_curr = self._halfedges['next'][_twin]
|
|
1846
|
+
if self._halfedges['twin'][_next] == -1:
|
|
1847
|
+
_curr = _next
|
|
1848
|
+
if self._halfedges['twin'][_twin] == -1:
|
|
1849
|
+
_curr = _twin
|
|
1850
|
+
|
|
1851
|
+
# We check open_chain instead of _curr != root since open_chain accounts for closed polygons
|
|
1852
|
+
# ending at an isolated singular vertex (two boundaries emit from a single vertex)
|
|
1853
|
+
open_chain = (self._halfedges['vertex'][self._halfedges['prev'][_root]] != self._halfedges['vertex'][curr_polygon[-1]])
|
|
1854
|
+
|
|
1855
|
+
# Loop around the boundary until we return the initial halfedge
|
|
1856
|
+
max_polygon_size = 100 # Put an upper limit on the polygon sizes so we don't look forever
|
|
1857
|
+
while open_chain and (max_polygon_size > 0):
|
|
1858
|
+
# We're not always guaranteed to hit the boundary again right
|
|
1859
|
+
# away doing next-twin-next, so wait until we do
|
|
1860
|
+
twin_next_attemps = 2*BOUNDARY_VALENCE
|
|
1861
|
+
while (twin_next_attemps > 0) and (self._halfedges['twin'][_curr] != -1):
|
|
1862
|
+
|
|
1863
|
+
# Need to do twin next in between boundary edges
|
|
1864
|
+
_twin = self._halfedges['twin'][_curr]
|
|
1865
|
+
_curr = self._halfedges['next'][_twin]
|
|
1866
|
+
if _curr == -1:
|
|
1867
|
+
# We hit a isolated singular vertex
|
|
1868
|
+
break
|
|
1869
|
+
twin_next_attemps -= 1
|
|
1870
|
+
|
|
1871
|
+
if _curr == -1:
|
|
1872
|
+
break
|
|
1873
|
+
|
|
1874
|
+
curr_polygon.append(_curr)
|
|
1875
|
+
|
|
1876
|
+
_next = self._halfedges['next'][_curr]
|
|
1877
|
+
_twin = self._halfedges['twin'][_next]
|
|
1878
|
+
_curr = self._halfedges['next'][_twin]
|
|
1879
|
+
|
|
1880
|
+
if self._halfedges['twin'][_next] == -1:
|
|
1881
|
+
_curr = _next
|
|
1882
|
+
if self._halfedges['twin'][_twin] == -1:
|
|
1883
|
+
_curr = _twin
|
|
1884
|
+
|
|
1885
|
+
open_chain = (self._halfedges['vertex'][self._halfedges['prev'][_root]] != self._halfedges['vertex'][curr_polygon[-1]])
|
|
1886
|
+
max_polygon_size -= 1
|
|
1887
|
+
|
|
1888
|
+
if max_polygon_size == 0:
|
|
1889
|
+
print('Polygon search took too long. Canceled.')
|
|
1890
|
+
|
|
1891
|
+
if ((_curr == -1) and (open_chain)) or (max_polygon_size == 0):
|
|
1892
|
+
# We failed to find a boundary polygon
|
|
1893
|
+
boundary_edges.pop()
|
|
1894
|
+
continue
|
|
1895
|
+
else:
|
|
1896
|
+
# We found a boundary polygon!
|
|
1897
|
+
boundary_polygons.append(curr_polygon)
|
|
1898
|
+
# Delete the polygon from the potential polygon list
|
|
1899
|
+
for edge in curr_polygon:
|
|
1900
|
+
if edge in boundary_edges:
|
|
1901
|
+
boundary_edges.remove(edge)
|
|
1902
|
+
|
|
1903
|
+
return boundary_polygons
|
|
1904
|
+
|
|
1905
|
+
def _fill_triangle(self, h0, h1, h2):
|
|
1906
|
+
"""
|
|
1907
|
+
Create a triangle inside three halfedges.
|
|
1908
|
+
|
|
1909
|
+
Note that this does not update the vertices or faces.
|
|
1910
|
+
"""
|
|
1911
|
+
|
|
1912
|
+
_h0_twin_vertex = self._halfedges['vertex'][h2]
|
|
1913
|
+
_, _h0_twin = self._new_edge(_h0_twin_vertex, twin=h0)
|
|
1914
|
+
_, _face = self._new_face(_h0_twin)
|
|
1915
|
+
_h1_twin_vertex = self._halfedges['vertex'][h0]
|
|
1916
|
+
_, _h1_twin = self._new_edge(_h1_twin_vertex, twin=h1, face=_face, next=_h0_twin)
|
|
1917
|
+
self._halfedges['face'][_h0_twin] = _face
|
|
1918
|
+
self._halfedges['prev'][_h0_twin] = _h1_twin
|
|
1919
|
+
_h2_twin_vertex = self._halfedges['vertex'][h1]
|
|
1920
|
+
_, _h2_twin = self._new_edge(_h2_twin_vertex, twin=h2, face=_face, prev=_h0_twin, next=_h1_twin)
|
|
1921
|
+
self._halfedges['next'][_h0_twin] = _h2_twin
|
|
1922
|
+
self._halfedges['prev'][_h1_twin] = _h2_twin
|
|
1923
|
+
|
|
1924
|
+
if h0 != -1:
|
|
1925
|
+
self._halfedges['twin'][h0] = _h0_twin
|
|
1926
|
+
if h1 != -1:
|
|
1927
|
+
self._halfedges['twin'][h1] = _h1_twin
|
|
1928
|
+
if h2 != -1:
|
|
1929
|
+
self._halfedges['twin'][h2] = _h2_twin
|
|
1930
|
+
|
|
1931
|
+
def _fan_triangulation(self, polygon):
|
|
1932
|
+
"""
|
|
1933
|
+
Triangulate a polygon, defined by halfedges in the mesh,
|
|
1934
|
+
by creating a fan emanating from a single vertex out to
|
|
1935
|
+
the boundary halfedges.
|
|
1936
|
+
|
|
1937
|
+
Parameters
|
|
1938
|
+
----------
|
|
1939
|
+
polygon : list
|
|
1940
|
+
List of halfedges defining a closed polygon in the mesh.
|
|
1941
|
+
"""
|
|
1942
|
+
|
|
1943
|
+
if len(polygon) < 3:
|
|
1944
|
+
raise ValueError('polygon must be of length >= 3')
|
|
1945
|
+
|
|
1946
|
+
# Reverse order
|
|
1947
|
+
polygon = polygon[::-1]
|
|
1948
|
+
|
|
1949
|
+
while len(polygon) > 0:
|
|
1950
|
+
h0 = polygon.pop()
|
|
1951
|
+
h1 = polygon.pop()
|
|
1952
|
+
if (len(polygon) == 1):
|
|
1953
|
+
# Base case, make a triangle
|
|
1954
|
+
# polygon = polygon[::-1]
|
|
1955
|
+
h2 = polygon.pop()
|
|
1956
|
+
self._fill_triangle(h0,h1,h2)
|
|
1957
|
+
_h0_twin = self._halfedges['twin'][h0]
|
|
1958
|
+
else:
|
|
1959
|
+
# Create a new triangle with a boundary edge
|
|
1960
|
+
self._fill_triangle(h0, h1, -1)
|
|
1961
|
+
|
|
1962
|
+
# Due to the way fill_triangle works, we need to set h0's vertex after the fact
|
|
1963
|
+
_h0_twin = self._halfedges['twin'][h0]
|
|
1964
|
+
self._halfedges['vertex'][_h0_twin] = self._halfedges['vertex'][self._halfedges['prev'][h0]]
|
|
1965
|
+
|
|
1966
|
+
# Adjust the boundary
|
|
1967
|
+
polygon.append(self._halfedges['next'][_h0_twin])
|
|
1968
|
+
|
|
1969
|
+
# Update faces and vertices
|
|
1970
|
+
self._update_face_normals([self._halfedges['face'][_h0_twin], self._halfedges['face'][h0], self._halfedges['face'][h1]])
|
|
1971
|
+
self._update_vertex_neighbors([self._halfedges['vertex'][_h0_twin], self._halfedges['vertex'][h0], self._halfedges['vertex'][h1]])
|
|
1972
|
+
|
|
1973
|
+
def _zig_zag_triangulation(self, polygon):
|
|
1974
|
+
"""
|
|
1975
|
+
Triangulate a polygon, defined by halfedges in the mesh,
|
|
1976
|
+
by creating zig-zagging between edges of the boundary.
|
|
1977
|
+
|
|
1978
|
+
Parameters
|
|
1979
|
+
----------
|
|
1980
|
+
polygon : list
|
|
1981
|
+
List of halfedges defining a closed polygon in the mesh.
|
|
1982
|
+
"""
|
|
1983
|
+
|
|
1984
|
+
if len(polygon) < 3:
|
|
1985
|
+
raise ValueError('polygon must be of length >= 3')
|
|
1986
|
+
|
|
1987
|
+
# Reverse order
|
|
1988
|
+
polygon = polygon[::-1]
|
|
1989
|
+
odd = True
|
|
1990
|
+
|
|
1991
|
+
while len(polygon) > 0:
|
|
1992
|
+
h0 = polygon.pop()
|
|
1993
|
+
h1 = polygon.pop()
|
|
1994
|
+
if (len(polygon) == 1):
|
|
1995
|
+
# Base case, make a triangle
|
|
1996
|
+
# polygon = polygon[::-1]
|
|
1997
|
+
h2 = polygon.pop()
|
|
1998
|
+
self._fill_triangle(h0,h1,h2)
|
|
1999
|
+
_h0_twin = self._halfedges['twin'][h0]
|
|
2000
|
+
else:
|
|
2001
|
+
# Create a new triangle with a boundary edge
|
|
2002
|
+
self._fill_triangle(h0, h1, -1)
|
|
2003
|
+
|
|
2004
|
+
# Due to the way fill_triangle works, we need to set h0's vertex after the fact
|
|
2005
|
+
_h0_twin = self._halfedges['twin'][h0]
|
|
2006
|
+
self._halfedges['vertex'][_h0_twin] = self._halfedges['vertex'][self._halfedges['prev'][h0]]
|
|
2007
|
+
|
|
2008
|
+
# Adjust the boundary
|
|
2009
|
+
polygon = polygon[::-1] # zig-zag
|
|
2010
|
+
if odd:
|
|
2011
|
+
polygon.insert(-1,self._halfedges['next'][_h0_twin])
|
|
2012
|
+
else:
|
|
2013
|
+
polygon.append(self._halfedges['next'][_h0_twin])
|
|
2014
|
+
odd = (not odd)
|
|
2015
|
+
|
|
2016
|
+
# Update faces and vertices
|
|
2017
|
+
self._update_face_normals([self._halfedges['face'][_h0_twin], self._halfedges['face'][h0], self._halfedges['face'][h1]])
|
|
2018
|
+
self._update_vertex_neighbors([self._halfedges['vertex'][_h0_twin], self._halfedges['vertex'][h0], self._halfedges['vertex'][h1]])
|
|
2019
|
+
|
|
2020
|
+
def _fill_holes(self, method='zig-zag'):
|
|
2021
|
+
"""
|
|
2022
|
+
Fill holes in the mesh.
|
|
2023
|
+
|
|
2024
|
+
Parameters
|
|
2025
|
+
----------
|
|
2026
|
+
method : string
|
|
2027
|
+
The method to use to patch boundary polygons. A string listed
|
|
2028
|
+
in options.
|
|
2029
|
+
"""
|
|
2030
|
+
|
|
2031
|
+
options = ['fan', 'zig-zag']
|
|
2032
|
+
|
|
2033
|
+
if method not in options:
|
|
2034
|
+
print('Unknown triangulation method. Using default.')
|
|
2035
|
+
method = 'zig-zag'
|
|
2036
|
+
|
|
2037
|
+
boundary_polygons = self._find_boundary_polygons()
|
|
2038
|
+
|
|
2039
|
+
# Find length 2 polygons and zipper them ("pinch")
|
|
2040
|
+
to_remove = []
|
|
2041
|
+
for polygon in boundary_polygons:
|
|
2042
|
+
if len(polygon) == 2:
|
|
2043
|
+
self._halfedges['twin'][polygon[0]] = polygon[1]
|
|
2044
|
+
self._halfedges['twin'][polygon[1]] = polygon[0]
|
|
2045
|
+
to_remove.append(polygon)
|
|
2046
|
+
|
|
2047
|
+
# Eliminate the length 2 polygons before triangulation
|
|
2048
|
+
for polygon in to_remove:
|
|
2049
|
+
boundary_polygons.remove(polygon)
|
|
2050
|
+
|
|
2051
|
+
# Triangulate
|
|
2052
|
+
if method == 'fan':
|
|
2053
|
+
for polygon in boundary_polygons:
|
|
2054
|
+
self._fan_triangulation(polygon)
|
|
2055
|
+
|
|
2056
|
+
if method == 'zig-zag':
|
|
2057
|
+
for polygon in boundary_polygons:
|
|
2058
|
+
self._zig_zag_triangulation(polygon)
|
|
2059
|
+
|
|
2060
|
+
# Reset display
|
|
2061
|
+
self._faces_by_vertex = None
|
|
2062
|
+
|
|
2063
|
+
def _connect_neighbors(self, _nn, starting_component=0):
|
|
2064
|
+
"""
|
|
2065
|
+
Connect the neighbors in the list _nn starting from
|
|
2066
|
+
starting_component.
|
|
2067
|
+
|
|
2068
|
+
Parameters
|
|
2069
|
+
----------
|
|
2070
|
+
_nn : list
|
|
2071
|
+
List of neighbors to connect.
|
|
2072
|
+
starting_component : int
|
|
2073
|
+
Component numbering to start at.
|
|
2074
|
+
|
|
2075
|
+
Returns
|
|
2076
|
+
-------
|
|
2077
|
+
component : int
|
|
2078
|
+
Maxmimum component number used to group neighbors.
|
|
2079
|
+
"""
|
|
2080
|
+
# Assign components via the 1-neighbors
|
|
2081
|
+
component = starting_component
|
|
2082
|
+
for _neighbor in _nn:
|
|
2083
|
+
singular = (_neighbor in self.singular_edges)
|
|
2084
|
+
if singular:
|
|
2085
|
+
component += 1
|
|
2086
|
+
if self._faces['component'][self._halfedges['face'][_neighbor]] != -1:
|
|
2087
|
+
continue
|
|
2088
|
+
self._faces['component'][self._halfedges['face'][_neighbor]] = component
|
|
2089
|
+
|
|
2090
|
+
# Extra check in case we're examining the edges out of order
|
|
2091
|
+
if singular:
|
|
2092
|
+
component += 1
|
|
2093
|
+
|
|
2094
|
+
# Loop through the neighbors again and group connected components
|
|
2095
|
+
for _neighbor in _nn:
|
|
2096
|
+
if (_neighbor in self.singular_edges):
|
|
2097
|
+
continue
|
|
2098
|
+
|
|
2099
|
+
_twin_neighbor = self._halfedges['twin'][_neighbor]
|
|
2100
|
+
if _twin_neighbor != -1:
|
|
2101
|
+
_face = self._halfedges['face'][_neighbor]
|
|
2102
|
+
_twin_face = self._halfedges['face'][_twin_neighbor]
|
|
2103
|
+
min_component = np.min([self._faces['component'][_face], self._faces['component'][_twin_face]])
|
|
2104
|
+
self._faces['component'][_face] = min_component
|
|
2105
|
+
self._faces['component'][_twin_face] = min_component
|
|
2106
|
+
|
|
2107
|
+
return component
|
|
2108
|
+
|
|
2109
|
+
def _group_neighbors(self, _vertex):
|
|
2110
|
+
"""
|
|
2111
|
+
Group faces around the 1-neighbor ring of a vertex by reachability.
|
|
2112
|
+
Faces are not reachable if they are separated by a singular edge.
|
|
2113
|
+
|
|
2114
|
+
Parameters
|
|
2115
|
+
----------
|
|
2116
|
+
_vertex : int
|
|
2117
|
+
Index of vertex at the center of a 1-neighbor ring.
|
|
2118
|
+
"""
|
|
2119
|
+
|
|
2120
|
+
# Zero out connectivity
|
|
2121
|
+
self._vertices['component'][:] = -1
|
|
2122
|
+
self._faces['component'][:] = -1
|
|
2123
|
+
self._halfedges['component'][:] = -1
|
|
2124
|
+
|
|
2125
|
+
# Grab the neighbors
|
|
2126
|
+
_nn = self._vertices[_vertex]['neighbors']
|
|
2127
|
+
_nn = _nn[_nn!=-1]
|
|
2128
|
+
|
|
2129
|
+
# Connect the neighbors
|
|
2130
|
+
component = self._connect_neighbors(_nn, 0)
|
|
2131
|
+
|
|
2132
|
+
# Don't trust self._vertices['neighbors'] because we're dealing with
|
|
2133
|
+
# singular vertices
|
|
2134
|
+
_twin_nn = list(np.where(self._halfedges['vertex'] == _vertex)[0])
|
|
2135
|
+
|
|
2136
|
+
# If there were any incident vertices not included in this list, group them, too
|
|
2137
|
+
_remaining = list(set(_twin_nn) - set(self._halfedges['twin'][_nn]) - set([-1]))
|
|
2138
|
+
|
|
2139
|
+
if _remaining:
|
|
2140
|
+
component += 1 # We're off the original 1-neighbor ring, so disconnect
|
|
2141
|
+
self._connect_neighbors(_remaining, component)
|
|
2142
|
+
|
|
2143
|
+
def _remove_singularities(self):
|
|
2144
|
+
"""
|
|
2145
|
+
Excise singularities from the mesh. This essentially unzips multivalent
|
|
2146
|
+
edges from the rest of the mesh, creating new, smaller connected
|
|
2147
|
+
components, each of which is free of singularities. This follows the
|
|
2148
|
+
local method for cutting outlined in Gueziec et al.
|
|
2149
|
+
|
|
2150
|
+
References
|
|
2151
|
+
----------
|
|
2152
|
+
Gueziec et al. Cutting and Stitching: Converting Sets of Polygons
|
|
2153
|
+
to Manifold Surfaces, IEEE TRANSACTIONS ON VISUALIZATION AND
|
|
2154
|
+
COMPUTER GRAPHICS, 2001.
|
|
2155
|
+
"""
|
|
2156
|
+
|
|
2157
|
+
# Make sure we're working with the latest singular edges/vertices
|
|
2158
|
+
self._singular_edges = None
|
|
2159
|
+
self._singular_vertices = None
|
|
2160
|
+
|
|
2161
|
+
# For each marked vertex, partition the faces of the 1-neighbor ring
|
|
2162
|
+
# into nc "is reachable" equivalence classes. Faces are reachable if
|
|
2163
|
+
# they share a non-singular edge incident on the marked vertex.
|
|
2164
|
+
for _vertex in self.singular_vertices[::-1]:
|
|
2165
|
+
|
|
2166
|
+
_twin_nn = list(np.where(self._halfedges['vertex'] == _vertex)[0])
|
|
2167
|
+
_update_vertices = np.hstack([_vertex, self._halfedges['vertex'][self._halfedges['twin'][_twin_nn]]])
|
|
2168
|
+
# _update_faces = self._faces[self._halfedges['face'][_twin_nn]]
|
|
2169
|
+
|
|
2170
|
+
self._group_neighbors(_vertex)
|
|
2171
|
+
|
|
2172
|
+
# Create nc-1 copies of the vertex and assign each equivalence class
|
|
2173
|
+
# one of these vertices. (All but one component gets a new vertex).
|
|
2174
|
+
components = np.unique(self._faces['component'][self._faces['component'] != -1])
|
|
2175
|
+
for c in components[1:]:
|
|
2176
|
+
_faces = self._faces[self._faces['component'] == c]
|
|
2177
|
+
_edges = np.hstack([self._halfedges['prev'][_faces['halfedge']], _faces['halfedge'], self._halfedges['next'][_faces['halfedge']]])
|
|
2178
|
+
_vertices = self._halfedges['vertex'][_edges]
|
|
2179
|
+
|
|
2180
|
+
_modified_edges = _edges[_vertices == _vertex]
|
|
2181
|
+
|
|
2182
|
+
_edge = self._halfedges['next'][_modified_edges[0]]
|
|
2183
|
+
|
|
2184
|
+
_, _new_vertex = self._new_vertex(self._vertices['position'][_vertex], halfedge=_edge)
|
|
2185
|
+
# Assign edges in this component connected to _vertex to _new_vertex
|
|
2186
|
+
self._halfedges['vertex'][_modified_edges] = _new_vertex
|
|
2187
|
+
|
|
2188
|
+
# Disconnect the boundaries of the components
|
|
2189
|
+
# Any edge pointing to the new vertex with a twin in a
|
|
2190
|
+
# different component must be reassigned
|
|
2191
|
+
_twin_face_component = self._faces['component'][self._halfedges['face'][self._halfedges['twin'][_modified_edges]]]
|
|
2192
|
+
_boundary = (_twin_face_component != c)
|
|
2193
|
+
_twin_edges = self._halfedges['twin'][_modified_edges[_boundary]]
|
|
2194
|
+
_twin_edges_mask = (_twin_edges != -1)
|
|
2195
|
+
_valid_edges = _modified_edges[_boundary][_twin_edges_mask]
|
|
2196
|
+
self._halfedges['twin'][_twin_edges[_twin_edges_mask]] = -1
|
|
2197
|
+
self._halfedges['twin'][_valid_edges] = -1
|
|
2198
|
+
# Any edge emanating from the new vertex with a twin in a
|
|
2199
|
+
# different component must be reassigned
|
|
2200
|
+
_twin_next_face_component = self._faces['component'][self._halfedges['face'][self._halfedges['twin'][self._halfedges['next'][_modified_edges]]]]
|
|
2201
|
+
_boundary = (_twin_next_face_component != c)
|
|
2202
|
+
_twin_next_edges = self._halfedges['twin'][self._halfedges['next'][_modified_edges[_boundary]]]
|
|
2203
|
+
_twin_next_edges_mask = (_twin_next_edges != -1)
|
|
2204
|
+
_valid_edges = self._halfedges['next'][_modified_edges[_boundary][_twin_next_edges_mask]]
|
|
2205
|
+
self._halfedges['twin'][_twin_next_edges[_twin_next_edges_mask]] = -1
|
|
2206
|
+
self._halfedges['twin'][_valid_edges] = -1
|
|
2207
|
+
|
|
2208
|
+
# Double check that the original vertex did not have a halfedge
|
|
2209
|
+
# in this component
|
|
2210
|
+
_safe_faces_bool = (self._faces['component'][self._halfedges['face']] != c)
|
|
2211
|
+
_safe_vertices = np.where((self._halfedges['vertex'] == _vertex) & (_safe_faces_bool))[0]
|
|
2212
|
+
_safe_halfedge = self._halfedges['next'][_safe_vertices[0]]
|
|
2213
|
+
self._vertices['halfedge'][_vertex] = _safe_halfedge
|
|
2214
|
+
|
|
2215
|
+
# self._update_face_normals(_update_faces)
|
|
2216
|
+
self._update_vertex_neighbors(np.hstack([_update_vertices, _new_vertex]))
|
|
2217
|
+
|
|
2218
|
+
# Reset
|
|
2219
|
+
self._singular_edges = None
|
|
2220
|
+
self._singular_vertices = None
|
|
2221
|
+
|
|
2222
|
+
def repair(self):
|
|
2223
|
+
"""
|
|
2224
|
+
Repair the mesh so it's topologically manifold.
|
|
2225
|
+
|
|
2226
|
+
References
|
|
2227
|
+
----------
|
|
2228
|
+
M. Attene, A lightweight approach to repairing digitized polygon
|
|
2229
|
+
meshes, The Visual Computer, 2010.
|
|
2230
|
+
"""
|
|
2231
|
+
|
|
2232
|
+
# 1. Remove singularities (singular edges and isolated singular vertices)
|
|
2233
|
+
self._remove_singularities()
|
|
2234
|
+
|
|
2235
|
+
# 2. Remove all connected components except the largest
|
|
2236
|
+
self.keep_largest_connected_component()
|
|
2237
|
+
|
|
2238
|
+
# 3. Patch mesh holes with new triangles
|
|
2239
|
+
self._fill_holes()
|
|
2240
|
+
|
|
2241
|
+
self._manifold = None
|
|
2242
|
+
|
|
2243
|
+
def to_stl(self, filename):
|
|
2244
|
+
"""
|
|
2245
|
+
Save list of triangles to binary STL file.
|
|
2246
|
+
|
|
2247
|
+
Parameters
|
|
2248
|
+
----------
|
|
2249
|
+
filename : string
|
|
2250
|
+
File name to write
|
|
2251
|
+
|
|
2252
|
+
Returns
|
|
2253
|
+
-------
|
|
2254
|
+
None
|
|
2255
|
+
"""
|
|
2256
|
+
from PYME.IO.FileUtils import stl
|
|
2257
|
+
|
|
2258
|
+
dt = np.dtype([('normal', '3f4'), ('vertex0', '3f4'), ('vertex1', '3f4'),
|
|
2259
|
+
('vertex2', '3f4'), ('attrib', 'u2')])
|
|
2260
|
+
|
|
2261
|
+
triangles_stl = np.zeros(self.face_normals.shape[0], dtype=dt)
|
|
2262
|
+
triangles_stl['vertex0'] = self.vertices[self.faces[:, 0]]
|
|
2263
|
+
triangles_stl['vertex1'] = self.vertices[self.faces[:, 1]]
|
|
2264
|
+
triangles_stl['vertex2'] = self.vertices[self.faces[:, 2]]
|
|
2265
|
+
triangles_stl['normal'] = self.face_normals
|
|
2266
|
+
|
|
2267
|
+
stl.save_stl_binary(filename, triangles_stl)
|
|
2268
|
+
|
|
2269
|
+
def to_ply(self, filename, colors=None):
|
|
2270
|
+
"""
|
|
2271
|
+
Save a list of triangles and their vertex colors to a PLY file.
|
|
2272
|
+
We assume colors are a Nx3 array where N is the number of vertices.
|
|
2273
|
+
"""
|
|
2274
|
+
from PYME.IO.FileUtils import ply
|
|
2275
|
+
|
|
2276
|
+
# Construct a re-indexing for non-negative vertices
|
|
2277
|
+
live_vertices = np.flatnonzero(self._vertices['halfedge'] != -1)
|
|
2278
|
+
new_vertex_indices = np.arange(live_vertices.shape[0])
|
|
2279
|
+
vertex_lookup = np.zeros(self._vertices.shape[0], dtype=int)
|
|
2280
|
+
|
|
2281
|
+
vertex_lookup[live_vertices] = new_vertex_indices
|
|
2282
|
+
|
|
2283
|
+
# Grab the faces and vertices we want
|
|
2284
|
+
faces = vertex_lookup[self.faces]
|
|
2285
|
+
vertices = self._vertices['position'][live_vertices]
|
|
2286
|
+
|
|
2287
|
+
ply.save_ply(filename, vertices, faces, colors)
|